Blog Nota técnica

Nuestra imagen principal desaparecía en iPhone: un bloqueador de anuncios la escondía

Durante semanas nuestra imagen principal desapareció en iPhone y el laboratorio no lo reproducía. La causa: un bloqueador de anuncios que escondía toda clase que empezara por ad-.

El síntoma

En tres páginas de este sitio, el bloque principal (el titular y la fotografía de la parte superior) no aparecía cuando la página se pintaba por primera vez en un iPhone. La sección de debajo quedaba en la parte superior de la pantalla durante un segundo y medio, y entonces el bloque aparecía encima. Al recargar la página, estaba al instante. Al abrirla de nuevo en una pestaña privada, volvía a faltar.

Solo ocurría en iPhone. Ocurría en Safari y en todos los demás navegadores de iOS, lo cual tiene sentido porque Apple obliga a todos a usar WebKit. Nunca ocurrió en un escritorio ni en un teléfono Android.

Tardamos semanas en encontrarlo. Por el camino publicamos varias correcciones. Cada una partía de una teoría razonable sobre la carga de imágenes, y ninguna era la causa.

Las teorías equivocadas

Todo en el síntoma apuntaba a la imagen: una fotografía que llegaba tarde, sin espacio reservado, y que empujaba el contenido hacia abajo al cargar. Así que reservamos el espacio con ancho, alto y relación de aspecto. Precargamos la fotografía desde la cabecera del documento. Rellenamos el espacio reservado con un marcador desenfocado para que nunca quedara en blanco. Cada cambio mejoró la página de forma medible, y ninguno tocó el error.

Intentamos reproducirlo en Chromium con la red limitada a una conexión de teléfono lenta. El bloque principal se maquetaba desde el primer fotograma. Instalamos WebKit real en un servidor y retuvimos cada imagen y cada fuente dos segundos y medio. Mismo resultado. En el laboratorio, la página estaba bien.

Medirlo donde ocurre

En ese punto dejamos de adivinar. Añadimos a la página un pequeño script temporal, solo para teléfonos, que registraba dónde estaba realmente el bloque principal cada cien milisegundos durante los primeros cinco segundos y luego enviaba una línea a nuestro servidor. Todo cabía en menos de cincuenta líneas.

Ver el código Ocultar el código JavaScript, 17 líneas
// Sample the hero's position for five seconds, then send one beacon.
const hero = document.querySelector("header.pagehead");
const samples = [];
const t0 = performance.now();
const tick = setInterval(() => {
  const r = hero.getBoundingClientRect();
  samples.push([
    Math.round(performance.now() - t0),
    Math.round(r.top), Math.round(r.height),
    getComputedStyle(hero).display,
    document.styleSheets.length,
  ]);
  if (samples.length >= 50) {
    clearInterval(tick);
    navigator.sendBeacon("/api/hero-trace.json", JSON.stringify(samples));
  }
}, 100);

Tres cargas de página desde el teléfono que mostraba el problema, y la respuesta estaba en los números.

Qué dijo el rastreo

A los 12 milisegundos de arrancar el script, el bloque principal medía 992 píxeles de alto, justo debajo de la barra de navegación, con dos hojas de estilo en la página. A los 114 milisegundos medía cero píxeles en la posición cero, su display calculado era none, y había cuatro hojas de estilo en la página. A los 1.554 milisegundos había vuelto.

Nada en la página añade hojas de estilo. Ninguna regla de nuestro CSS puede poner ese elemento en display none. Ningún script de la página lo toca. Así que algo externo a la página estaba inyectando dos hojas de estilo en la primera carga, y una de ellas escondía nuestro bloque principal.

El bloque principal de esas tres páginas tenía las clases ad-hero, ad-heroimg y ad-sub. El prefijo era una abreviatura de agencies y advocacy, dos de los mercados que atienden esas páginas. Un bloqueador de anuncios no sabe eso.

Los filtros cosméticos esconden los elementos cuyos nombres de clase empiezan por ad-, y los bloqueadores de contenido en iOS inyectan exactamente esas reglas como hojas de estilo cuando una página carga por primera vez. La segunda pasada, un segundo y medio después, restauraba el elemento, y por eso acababa apareciendo. Al recargar, las reglas del bloqueador ya estaban en su sitio, así que no había nada que parpadeara.

La corrección

Renombrar tres clases. El bloque principal ahora es mk-hero, mk-heroimg y mk-sub en las nueve páginas que las usaban, en los tres idiomas, y hay una nota al principio de la hoja de estilos para que nadie vuelva a nombrar una clase así. La corrección estaba en línea doce minutos después de que llegara el rastreo.

Ver el código Ocultar el código CSS, 3 líneas
/* Class names: never start one with "ad", "ads", "advert", "sponsor",
   "promo", "banner" or "popup". Ad blockers' cosmetic filters hide
   elements by such prefixes. */

Qué comprobar en su propio sitio

Abra el código fuente de cualquier página y busque valores de class= e id= que empiecen por ad, ads, advert, sponsor, promo, banner o popup. Los que más vemos en sitios de asociaciones y organizaciones sin fines de lucro son ad-banner para una barra de avisos, sponsor-logos para quienes pagaron la conferencia y promo para la campaña de afiliación. Una parte significativa de sus visitantes no ve ninguno de ellos, y usted no lo notará, porque en su computadora se ven bien.

Si algunos visitantes reportan un error que usted no puede reproducir, no publique otra teoría. Ponga una medición en la página durante un día. Es más pequeña que la corrección y le muestra lo que de verdad está pasando.

Reciba el LFW Weekly Recap

Un boletín que se envía cada viernes con la semana en LFW. Un correo a la semana y ningún otro artículo del blog.

Primero enviamos un correo de confirmación. No llega nada más hasta que haga clic en él.