javascript - Cómo se desborda: oculto; & border-radius en un contenedor causa ralentizaciones masivas en "Paint/Render Layer" dentro del contenedor, solo en IE?
css performance (2)
El problema fue llevado a la atención de Microsoft en este informe de errores escrito por Joppe Kroon en febrero de 2014:
La única respuesta significativa de Microsoft se publicó en abril de 2016. No proporciona una solución o una solución alternativa:[IE 9] [IE 11] div''s con un radio de borde y desbordamiento que no sea visible cambie de tamaño lentamente cuando se llena con div''s con posición relativa
El redibujado de la página funciona muy mal cuando hay muchos elementos DIV grandes con una combinación de posición "absoluta", "fija" o "relativa", un desbordamiento que no sea "visible" y un radio de borde. Esto se puede ver claramente al redimensionar la ventana o desplazarse. Los eventos de pintura individuales pueden tardar hasta 1,5 segundos en completarse, lo que hace que la página deje de responder.
Este problema de rendimiento aparece en IE 11 e IE 9 pero no en IE 10.
[...] Hemos analizado el problema que usted informó muy de cerca y encontramos que se parece mucho a problemas reportados similares. Ninguna de nuestras resoluciones habituales ("POR DISEÑO", "DUPLICADO", "FIJO", etc.) coincide exactamente con esta situación, por lo tanto, la estamos marcando como "NO ARREGLARÁ" por falta de algo mejor, pero lo que realmente queremos decir es que Trataremos de ello de una manera agregada en lugar de mirar este incidente exacto en esta situación exacta. [...]
Todo lo mejor, The MS Edge Team
El estado del error es: Cerrado como No arreglará
He tenido muchos problemas con el mal rendimiento en IE (todas las versiones, incluido IE11) en un widget basado en JavaScript HTML / SVG, solo cuando el widget está alojado en una página determinada.
Después de identificar que la causa principal de la ralentización era la pintura / capa de renderización redibujada, y agotando la información sobre estos que podía obtener de IE Developer Tools , recurrí a prueba y error desactivando las clases de antepasados de a uno por vez hasta que mejoró el rendimiento; luego, al identificar la clase, apaga las reglas de estilo de a una por vez.
Todo mi problema parece reducirse a un único overflow: hidden;
gobierna sobre un antepasado varios divs en el árbol.
La diferencia que hace es increíble: con overflow: hidden;
una simple interacción del usuario (destacando una ruta SVG, generando una etiqueta de texto HTML, mostrando la etiqueta y posicionándola en relación con la ruta SVG y el contenedor) maximiza el procesador, reduce la velocidad de cuadro UI a cero y congela todo muerto por entre 1,000 y 4,000 milisegundos por interacción. Sin overflow: hidden;
en el ancestro, se completa en decenas de milisegundos y la velocidad de fotogramas nunca desciende por debajo de la mitad (los navegadores que no sean IE son los mismos independientemente del overflow: hidden;
).
Aquí está el perfil con
overflow: hidden;
en el ancestro , perfilando interacciones dentro y fuera, filtrado para pintar eventos:Aquí está el perfil sin
overflow: hidden;
en el ancestro , perfilando interacciones dentro y fuera, filtrado para pintar eventos. El único cambio fue marcar o quitar la marca de la casilla junto aloverflow: hidden;
estilo en el inspector DOM, y no importa en qué orden hago las pruebas:
No quiero anular este overflow: hidden;
como un yeso pegajoso y decir el trabajo hecho, sin entender cómo sucede esto y arriesgándose a que el problema vuelva a ocurrir con otros cambios de CSS aparentemente triviales. Prefiero entender por qué overflow: hidden;
hace una gran diferencia y aborda eso de una manera robusta que funciona independientemente de la regla de desbordamiento aplicada.
Lamentablemente, no puedo publicar una demostración completa, pero aquí hay un resumen de la parte relevante de la estructura DOM con comentarios sobre los estilos relacionados con el diseño:
<div class="responsive-grid">
<!-- ...lots of nested divs that simply inherit styles, I can''t change this aspect of the Drupal layout -->
<div id="panel-5" class="col-12"> <!-- width: 100%; float: left -->
<!-- this is the first element IE looks at for offsetWidth when doing the changes below -->
<!-- ...a few more nested divs without layout-changing styles -->
<div class="panel"> <!-- overflow: hidden; clear: both; border: 1px; -->
<!-- this is the element where removing the overflow: hidden changes everything -->
<!-- I''m not sure what clear:both is for here, since no siblings. Seems redundant -->
<!-- ...a few more nested divs with no style rules, some contain <p>s <h2>s etc... -->
<div class="container"> <!-- position: relative; -->
<div class="sub-column col-8"> <!-- width: 66%; display: inline-block -->
<div class="square"> <!-- width: 100%; padding-bottom: 100%; position: relative -->
<svg viewbox="0 0 500 500" preserveAspectRatio="XMinYMin meet" ...>
<!-- svg position: absolute; width:100%; height: 100% -->
Many paths here
<div class="label"> <!-- fixed width in pixels; position: absolute -->
Some text here
</div>
</div>
</div>
<div class="sub-column col-4"> <!-- width: 33%; display: inline-block -->
<div class="sidebar">
Many interactive controls here
<!-- .square, svg andd .sidebar contain the only elements that are updated -->
</div>
</div>
</div>
<!-- some more ordinary position: static text containers -->
</div>
</div>
</div>
¿Qué podría estar pasando aquí, y hay alguna manera de evitarlo sin eliminar / prohibir el overflow: hidden;
en el elemento ancestro?
He visto Cómo evitar el costo de rendimiento del desbordamiento: ¿oculto? pero ambas preguntas y respuestas parecen ser específicas para las tablas HTML y un viejo error de Webkit desde que se corrigió.
También parecen específicos para los casos en que el contenido recortado por el desbordamiento se pinta innecesariamente; una peculiaridad de mi caso es que el overflow: hidden;
no está recortando mucho (si acaso) en esta página (pero no puedo eliminarlo simplemente porque es parte de una plantilla que afecta a cientos de otras páginas, donde sí tiene efecto).
Actualización: la trama se complica. Logré replicar el problema con mi widget en una estructura HTML más simple, y descubrí que el problema solo ocurre si overflow: hidden;
y border-radius
(en mi caso, 3px) se configuran en el mismo contenedor . Con uno pero no el otro, el problema desaparece.
Después de muchas más pruebas, creo que estoy empezando a entender qué está pasando aquí. Sin embargo, esto se basa puramente en la observación, por lo que aún estaría interesado en obtener una respuesta más autorizada si alguien tiene una.
¿Qué lo causa?
Parece suceder solo si todo esto es cierto:
- El navegador es IE (cualquier versión)
- Un elemento ancestro al que llamaremos X contiene ambos
overflow: hidden
yborder-radius
(o-ms-border-radius
). En mi prueba, no sucede si diferentes ancestros en la misma rama tienen estos estilos. - Hay muchos elementos complejos como rutas SVG o% -width divs que se encuentran en una rama DOM donde ellos, o un elemento entre ellos y el elemento X, tienen
position: absolute;
oposition: relative;
El problema también parece ser más pronunciado en proporción al número de elementos afectados por la position: absolute;
/ relative
y su complejidad. En los casos en los que había rutas SVG en un contenedor SVG de% de ancho de escala receptivo con% de relleno inferior para una relación de aspecto fija, por ejemplo, el problema era muy pronunciado; si a esta rama se le dio una position: static
pero otra rama tenía% -width divs con una position: absolute;
ancestro, entonces el problema todavía era observable en comparación con la eliminación de uno de overflow: hidden;
o border-radius
, pero fue mucho menos severo.
¿Pero por qué?
No tengo una respuesta definitiva, pero tengo una teoría plausible que parece ajustarse a los hechos. Irónicamente, sería un intento fallido de optimización del rendimiento por parte de IE.
offsetWidth
que los cálculos de offsetWidth
para los elementos entre X y las rutas iban hasta las rutas, lo cual no tenía sentido para mí y provocó una pregunta relacionada porque las rutas dentro de un contenedor SVG seguramente no pueden influir en el diseño fuera del contenedor.
También me di cuenta al investigar esto que otros navegadores, particularmente una versión anterior de Chrome, parecían tener un problema diferente : los elementos que deberían haberse ocultado se estaban procesando, lo que causaba ralentizaciones.
Juntando esto, creo que hay una explicación plausible de lo que está sucediendo aquí. Si es cierto, irónicamente, mis problemas de rendimiento fueron causados por un intento de fuga de IE para optimizar el rendimiento y evitar problemas como el problema de Chrome corregido anteriormente.
Si esta teoría es cierta, algo como esto estaría sucediendo dentro de IE:
- Ve el
overflow: hidden;
y concluye que puede mejorar el rendimiento al identificar elementos que están totalmente fuera de los límites del elemento antes de realizar eventos de redibujado / reflujo / pintura, etc. sobre ellos. - Ve la
position: absolute;
yposition: relative;
más abajo en el DOM y concluye que estos y sus hijos podrían estar totalmente fuera del contenedor y podrían ser descartados como este - Durante eventos como redibujar y refluir, primero recorre todos estos elementos y comprueba que no están totalmente fuera de los límites. Esto todavía ocurre incluso sin
border-radius
, pero en tales casos es trivial y toma milisegundos. - ... sin embargo, IE nota el radio del borde y concluye que la forma de este
overflow: hidden
elementooverflow: hidden
no es un rectángulo y, por lo tanto, se necesita un cálculo más complejo para determinar qué está fuera de los límites - Presumiblemente, también concluye que las rutas SVG deben compararse en función de sus formas reales, no de sus simples coordenadas de caja delimitadora, ya que esto ya no es una simple cuestión de comparar rectángulos paralelos.
- De repente, el cálculo para probar si un elemento puede descartarse durante la pintura / redibujado / reflujo se ha vuelto complejo. Si se repite cientos de veces para cientos de elementos, empeora masivamente el rendimiento en cada evento desencadenante.
¿Como arreglarlo?
Si puede, simplemente mueva el overflow: hidden
o border-radius
a un elemento secundario para que no estén ambos en el mismo elemento. Trabajo hecho.
Sin embargo, para mí, estoy creando un complemento que debe poder incluirse en cualquier lugar y no tendrá control sobre las páginas en las que se implementó. No estoy al tanto de ninguna forma en la que pueda obligar a IE a desactivar este comportamiento.
El mejor enfoque que puedo pensar es suponer que el estilo del border-radius
del border-radius
no es esencial para la estética y que el overflow: hidden;
podría ser esencial para la estructura, por lo que, si el navegador es IE, busque el árbol ancestro y elimine border-radius
del border-radius
de cualquier elemento que lo tenga y overflow: hidden;
.
Mi aplicación ya usa jQuery, por lo que esta prueba se ve así:
if( isAnyIE() ) {
$container.parentsUntil("body").filter(function(){
var $this = $(this),
overflow = $this.css(''overflow'');
return ( overflow === ''hidden'' && hasBorderRadius( $this ) );
}).addClass( ''remove-border-radius'' );
}
function hasBorderRadius( $element ){
function getNum( style ){
return parseFloat( $element.css( ''border-''+style+''-radius'' ) ) || 0;
}
var number = 0;
number += getNum( ''top-left'' );
number += getNum( ''bottom-left'' );
number += getNum( ''top-right'' );
number += getNum( ''bottom-right'' );
$element = null;
return !!number;
}
function isAnyIE(){
// isIE(): use conditional comments and classes, see https://.com/a/18615772/568458
// isIE10: use user agent like navigator.appVersion.indexOf("MSIE 10") !== -1;
// isIE11: use user agent like !!navigator.userAgent.match(/Trident.*rv[ :]*11/./);
return isIE11() || isIE10() || isIE();
}
Con CSS como:
.remove-border-radius {
border-radius: 0 0 0 0 !important;
-ms-border-radius: 0 0 0 0 !important;
}