Tutorial WordPress: Evitar que el header tape anclas internas con scroll-margin-top

·

·

Por qué ocurre el problema y solución recomendada

Cuando en WordPress (o en cualquier web) tienes un header fijo (position: fixed o position: sticky) y usas enlaces internos (anclas con href=#mi-seccion), el navegador desplaza la ventana de modo que el elemento objetivo quede en la parte superior del viewport. Si el header ocupa parte de la pantalla, cubrirá la sección anclada. La solución moderna y sencilla es usar la propiedad CSS scroll-margin-top aplicada al elemento objetivo para desplazar su punto de anclaje hacia abajo la cantidad del header. Es la técnica preferida por ser simple, performant y compatible con los navegadores modernos.

Cómo aplicarlo: la versión CSS básica

Identifica qué elementos reciben enlaces internos en tu sitio: normalmente son los títulos de entradas y secciones (h2, h3, h4) o bloques del editor. Aplica scroll-margin-top igual a la altura del header fijo.

/ Ejemplo básico: header fijo de 80px /
h2[id], h3[id], h4[id] {
  scroll-margin-top: 80px
}

/ Si usas bloques de Gutenberg con clase .wp-block-heading /
.wp-block-heading[id] {
  scroll-margin-top: 80px
}

Recomendación: usar una variable CSS para mantener fácil el ajuste

Define una variable con la altura del header para poder cambiarla en un solo sitio cuando el header cambie en móvil o escritorio.

:root {
  --site-header-height: 80px
}

/ Aplica la variable /
h2[id], h3[id], h4[id], .wp-block-heading[id] {
  scroll-margin-top: var(--site-header-height)
}

Casos con header de altura dinámica (ej. responsive, menús colapsables)

Si el header cambia de altura según el dispositivo o cuando el usuario abre/cierra el menú, la variable CSS fija puede no ser suficiente. Tres enfoques prácticos:

Ejemplo JavaScript: calcular y fijar scroll-margin-top dinámicamente

Este script detecta la altura real del header y aplica scroll-margin-top a elementos con id usados como objetivos de anclaje. Útil como fallback o para headers con altura variable.

/ Ajuste dinámico de scroll-margin-top según altura del header /
(function(){
  function ajustarOffsets() {
    var header = document.querySelector(.site-header)  document.querySelector(header)
    if (!header) return
    var offset = header.getBoundingClientRect().height
    var targets = document.querySelectorAll(h2[id], h3[id], h4[id], .wp-block-heading[id], [id].anchor-target)
    targets.forEach(function(el){
      el.style.scrollMarginTop = (offset   8)   px //  8px para un pequeño margen visual
    })
  }

  // Ejecutar al cargar y cuando cambie tamaño
  window.addEventListener(load, ajustarOffsets)
  window.addEventListener(resize, function(){
    // Debounce simple
    clearTimeout(window._ajustarOffsetsTimeout)
    window._ajustarOffsetsTimeout = setTimeout(ajustarOffsets, 120)
  })

  // Opcional: observar cambios en el DOM si el header cambia de tamaño por clases
  var header = document.querySelector(.site-header)  document.querySelector(header)
  if (header  window.MutationObserver) {
    var observer = new MutationObserver(function(){ ajustarOffsets() })
    observer.observe(header, { attributes: true, attributeFilter: [class, style] })
  }
})()

Alternativa: interceptar clicks en enlaces internos y desplazar con offset

En lugar de modificar los elementos objetivo, puedes interceptar los clicks en enlaces internos y desplazar manualmente restando el offset del header. Útil cuando no puedes modificar los objetivos o quieres animación personalizada.

/ Intercepta clicks en enlaces internos y aplica offset /
(function(){
  function getHeaderOffset(){
    var header = document.querySelector(.site-header)  document.querySelector(header)
    return header ? header.getBoundingClientRect().height : 0
  }

  document.addEventListener(click, function(e){
    var a = e.target.closest(a[href^=#])
    if (!a) return
    var hash = a.getAttribute(href)
    if (hash === #  hash === ) return
    var target = document.getElementById(hash.slice(1))
    if (!target) return

    // Evitar comportamiento por defecto y hacer scroll con offset
    e.preventDefault()
    var rect = target.getBoundingClientRect()
    var scrollTop = window.pageYOffset  document.documentElement.scrollTop
    var top = rect.top   scrollTop - getHeaderOffset() - 8 // 8px margen
    window.scrollTo({ top: top, behavior: smooth })

    // Actualizar hash sin salto inmediato (opcional)
    history.pushState(null, , hash)
    // Fijar foco al target para accesibilidad
    target.setAttribute(tabindex, -1)
    target.focus({ preventScroll: true })
  })
})()

Integración en WordPress: dónde añadir CSS y JS

  1. CSS: Appearance → Customize → Additional CSS, o en el archivo style.css de un child theme (si modificas un tema padre usa child theme).
  2. JS: Si solo son unas pocas líneas, puedes insertarlo con un plugin de snippets o mediante functions.php del child theme añadiendo un script encolado en el footer.

Ejemplo PHP: encolar un script en functions.php

/ functions.php - encolar script para ajustar scroll offsets /
function tema_personalizado_enqueue_scripts() {
  wp_enqueue_script(
    ajuste-scroll-offset,
    get_stylesheet_directory_uri() . /js/ajuste-scroll-offset.js,
    array(),
    1.0,
    true
  )
}
add_action(wp_enqueue_scripts, tema_personalizado_enqueue_scripts)

Compatibilidad y buenas prácticas

Trucos adicionales y casos especiales

Tabla comparativa rápida

Método Ventajas Inconvenientes
scroll-margin-top Simple, CSS-only, performante Requiere conocer la altura del header no funciona en navegadores muy antiguos
JS que aplica inline scrollMarginTop Dinámico, adapta a cambios de altura Dependiente de JavaScript
Interceptar clicks y calcular offset Total control sobre animación y offset Más código, debe mantener el focus/estado del hash

Ejemplos prácticos listos para usar

CSS recomendada (variable selectores comunes):

:root {
  --site-header-height: 72px / Ajusta según tu header /
}

/ Aplica a títulos y bloques con id (Gutenberg) /
h1[id], h2[id], h3[id], h4[id],
.wp-block-heading[id],
[id].anchor-target {
  scroll-margin-top: var(--site-header-height)
}

/ Ajuste opcional para móviles usando media queries /
@media (max-width: 768px) {
  :root { --site-header-height: 56px }
}

PHP para encolar el script (añádelo en functions.php del child theme):

function enqueue_custom_scroll_offset_script(){
  wp_enqueue_script(
    custom-scroll-offset,
    get_stylesheet_directory_uri() . /js/custom-scroll-offset.js,
    array(),
    1.0,
    true
  )
}
add_action(wp_enqueue_scripts, enqueue_custom_scroll_offset_script)

JS que calcula el offset y aplica a los objetivos (guardar como js/custom-scroll-offset.js):

(function(){
  function setOffsets() {
    var header = document.querySelector(.site-header)  document.querySelector(header)
    if (!header) return
    var offset = header.getBoundingClientRect().height
    document.querySelectorAll(h1[id], h2[id], h3[id], h4[id], .wp-block-heading[id]).forEach(function(el){
      el.style.scrollMarginTop = (offset   6)   px
    })
  }
  window.addEventListener(load, setOffsets)
  window.addEventListener(resize, function(){
    clearTimeout(window._offsetTimer)
    window._offsetTimer = setTimeout(setOffsets, 120)
  })
})()

Resumen final

La forma más limpia y recomendable es usar scroll-margin-top aplicado a los elementos que son objetivos de anclas. Para headers de altura variable o situaciones complejas añade un pequeño script que calcule la altura real y establezca el offset dinámicamente. Integra el CSS en el customizer o en el child theme y encola el JavaScript desde functions.php. No olvides comprobar accesibilidad (gestión del foco) y probar en móvil y escritorio.



Leave a Reply

Your email address will not be published. Required fields are marked *