Forzar redibujar / actualizar DOM en Chrome / Mac


215

De vez en cuando, Chrome renderizará HTML / CSS perfectamente válido de forma incorrecta o no lo hará. Excavar a través del inspector DOM a menudo es suficiente para que se dé cuenta del error de sus formas y vuelva a dibujar correctamente, por lo que es probable que el marcado sea bueno. Esto sucede con frecuencia (y previsiblemente) lo suficiente en un proyecto en el que estoy trabajando que puse el código para forzar un redibujo en ciertas circunstancias.

Esto funciona en la mayoría de las combinaciones de navegador / sistema operativo:

    el.style.cssText += ';-webkit-transform:rotateZ(0deg)'
    el.offsetHeight
    el.style.cssText += ';-webkit-transform:none'

Al igual que en, modifique alguna propiedad CSS no utilizada, luego solicite información que obligue a volver a dibujar, luego destrabe la propiedad. Desafortunadamente, el brillante equipo detrás de Chrome para Mac parece haber encontrado una manera de compensar la altura sin volver a dibujar. Por lo tanto, matar a un truco útil.

Hasta ahora, lo mejor que se me ocurrió para obtener el mismo efecto en Chrome / Mac es este pedazo de fealdad:

    $(el).css("border", "solid 1px transparent");
    setTimeout(function()
    {
        $(el).css("border", "solid 0px transparent");
    }, 1000);

Como en, realmente fuerce al elemento a saltar un poco, luego enfríe un segundo y salte hacia atrás. Para empeorar las cosas, si deja ese tiempo de espera por debajo de 500 ms (hasta donde sería menos notable), a menudo no tendrá el efecto deseado, ya que el navegador no podrá volver a dibujar antes de volver a su estado original.

¿Alguien quiere ofrecer una mejor versión de este hack de redibujar / actualizar (preferiblemente basado en el primer ejemplo anterior) que funciona en Chrome / Mac?


Me encontré con el mismo problema hace unos minutos. Cambié el elemento por un div (era un lapso) y ahora el navegador redibuja los cambios correctamente. Sé que esto es un poco viejo, pero creo que esto puede ayudar a algunos hermanos.
Andrés Torres

2
Consulte la respuesta a continuación relacionada con la opacidad 0.99, la mejor respuesta aquí, pero no es fácil de encontrar ya que es muy profunda en la página.
Grouchal

1
Esto también sucede en Linux :-(
schlingel

1
Puede reemplazar el tiempo de espera con un requestAnimationFrame, en cuyo caso logrará lo mismo pero con un retraso de 16 ms en lugar de 1000 ms.
trusktr

3
Presente un problema de Chromium para la representación no válida. Parece que nadie lo ha hecho, a pesar de que esto sucedió hace 4 años.
Bardi Harborow

Respuestas:


183

No estoy seguro de qué es exactamente lo que está tratando de lograr, pero este es un método que he utilizado en el pasado con éxito para forzar al navegador a volver a dibujar, tal vez funcione para usted.

// in jquery
$('#parentOfElementToBeRedrawn').hide().show(0);

// in plain js
document.getElementById('parentOfElementToBeRedrawn').style.display = 'none';
document.getElementById('parentOfElementToBeRedrawn').style.display = 'block';

Si este simple redibujo no funciona, puedes probar este. Inserta un nodo de texto vacío en el elemento que garantiza un nuevo dibujo.

var forceRedraw = function(element){

    if (!element) { return; }

    var n = document.createTextNode(' ');
    var disp = element.style.display;  // don't worry about previous display style

    element.appendChild(n);
    element.style.display = 'none';

    setTimeout(function(){
        element.style.display = disp;
        n.parentNode.removeChild(n);
    },20); // you can play with this timeout to make it as short as possible
}

EDITAR: En respuesta a Šime Vidas, lo que estamos logrando aquí sería un reflujo forzado. Puede obtener más información del propio maestro http://paulirish.com/2011/dom-html5-css3-performance/


3
Afaik, no es posible volver a dibujar solo una parte de la ventana gráfica. El navegador siempre vuelve a dibujar toda la página web.
Šime Vidas

38
en lugar de $ ('# parentOfElementToBeRedrawn'). hide (). show (); Necesitaba .hide.show (0) para funcionar correctamente en Chrome
l.poellabauer

1
@ l.poellabauer mismo aquí - esto no tiene sentido ... pero gracias. ¿Cómo diablos te enteraste? :)
JohnIdol

66
Para su información, el área relevante en la que estaba trabajando era una lista de desplazamiento infinita. Era un poco inviable ocultar / mostrar todo el UL, así que jugué y descubrí que si lo haces .hide().show(0)en cualquier elemento (elegí el pie de página) debería actualizar la página.
treeface

1
Es posible volver a dibujar solo una parte de la ventana gráfica. No hay API para hacerlo, pero el navegador puede elegir hacerlo. La ventana gráfica está pintada en mosaicos para empezar. Y las capas compuestas se pintan de forma independiente.
morewry

62

Ninguna de las respuestas anteriores funcionó para mí. Me di cuenta de que cambiar el tamaño de mi ventana causó un redibujo. Entonces esto lo hizo por mí:

$(window).trigger('resize');

11
pista: esto redibujará su ventana completa, que es costosa en rendimiento y probablemente no sea lo que OP quiere
hereandnow78

77
Cambiar el tamaño de la ventana siempre desencadena un reflujo, pero el código $(window).trigger('resize')no lo hace, solo lanza el evento 'redimensionar' que activa el controlador relacionado si está asignado.
guari

1
¡Me salvas una semana entera! Mi problema es que después de configurar <body style = "zoom: 67%">, la página se acercó a mi nivel esperado, pero la página está congelada y no se puede desplazar. Su respuesta resolvió este problema de inmediato
user1143669

30

Recientemente nos encontramos con esto y descubrimos que la promoción del elemento afectado a una capa compuesta con translateZ solucionó el problema sin necesidad de javascript adicional.

.willnotrender { 
   transform: translateZ(0); 
}

Como estos problemas de pintura se muestran principalmente en Webkit / Blink, y esta solución se dirige principalmente a Webkit / Blink, es preferible en algunos casos. Especialmente porque muchas soluciones de JavaScript causan un reflujo y repintado , no solo un repintado.


1
Esto funcionó bien para mí cuando el problema era un elemento HTML eliminado que se dibujaba en un lienzo a pesar de que el lienzo estaba continuamente animado.
Knaģis

Esto solucionó un problema de diseño que estaba teniendo con neon-animated-pages. Gracias: +1:
coffeesaga

1
Esto funcionó. Safari estaba dejando elementos que estaban en un conjunto div para mostrar ninguno, visible y no seleccionable. Cambiar el tamaño de la ventana los hizo desaparecer. Agregar esta transformación causó que los "artefactos" desaparecieran como se esperaba.
Jeff Mattson el

28

Esta solución sin tiempos de espera! ¡ Fuerza real redibujada! Para Android e iOS.

var forceRedraw = function(element){
  var disp = element.style.display;
  element.style.display = 'none';
  var trick = element.offsetHeight;
  element.style.display = disp;
};

1
Esto no funciona para mí en Chrome ni FF en Linux. Sin embargo simplemente accediendo a element.offsetHeight (sin modificar la propiedad de presentación) hace el trabajo. Es como si el navegador omitiera el cálculo de offsetHeight cuando el elemento no es visible.
Grodriguez

Esta solución funciona perfectamente para evitar un error que encontré en el navegador basado en Chrome utilizado en "overwolf". Estaba tratando de descubrir cómo hacer esto y me ahorraste el esfuerzo.
nacitar sevaht

13

Esto funciona para mi. Felicitaciones ve aquí .

jQuery.fn.redraw = function() {
    return this.hide(0, function() {
        $(this).show();
    });
};

$(el).redraw();

1
Pero dejó algunos 'restos' como 'display: block', etc. que a veces pueden destruir la estructura de la página.
pie6k

Esto debería ser esencialmente lo mismo que$().hide().show(0)
Mahn

@Mahn, ¿lo intentaste? En mi opinión, .hide () no bloquea, por lo que omitiría la ejecución de la representación en el navegador, es decir, todo el punto del método. (Y si no recuerdo mal, lo probé en ese entonces.)
zupa

2
@zupa está probado y funciona en Chrome 38. El truco es que show()es diferente show(0)al especificar una duración en este último, se desencadena por los métodos de animación jQuery en un tiempo de espera en lugar de inmediatamente, lo que da tiempo para renderizar de la misma manera que lo hide(0, function() { $(this).show(); })hace .
Mahn

@Mahn está bien, buena captura entonces. Definitivamente estoy a favor de su solución si funciona multiplataforma y no es una característica reciente de jQuery. Como no tengo tiempo para probarlo, lo agregaré como "puede funcionar". Puede editar la respuesta si cree que se aplica lo anterior.
zupa

9

Ocultar un elemento y luego mostrarlo nuevamente dentro de un setTimeout de 0 forzará un redibujo.

$('#page').hide();
setTimeout(function() {
    $('#page').show();
}, 0);

1
Este funcionó para un error de Chrome donde los filtros SVG a veces no se repintan, bugs.chromium.org/p/chromium/issues/detail?id=231560 . El tiempo de espera fue importante.
Jason D

Este es el que funcionó para mí al tratar de forzar a Chrome a recalcular los valores para myElement.getBoundingClientRect(), que estaban siendo almacenados en caché, probablemente para fines de optimización / rendimiento. Simplemente agregué un tiempo de espera de cero milisegundos para la función que agrega el nuevo elemento al DOM y obtiene los nuevos valores después de que el elemento anterior con los valores anteriores (en caché) se eliminó del DOM.
TheDarkIn1978

6

Esto parece hacer el truco para mí. Además, realmente no se muestra en absoluto.

$(el).css("opacity", .99);
setTimeout(function(){
   $(el).css("opacity", 1);
},20);

Funcionó para mí en Chromium Versión 60.0.3112.113 con respecto a este error: Problema 727076 . Eficiente y sutil; muchas gracias.
Lonnie Best

@ wiktus239 ¿Quizás 20 millis es demasiado rápido y el navegador no tiene tiempo para cambiar a .99, antes de que vuelva a cambiar a 1.0? - Este truco de opacidad funciona para mí de todos modos, para hacer que los contenidos en un iframe sean visibles en el Safari de iPhone iOS 12, y alternar cada 1000 milis. Eso también está en el problema vinculado 727076 en el comentario anterior (1000 milis).
KajMagnus

4

la llamada window.getComputedStyle()debe forzar un reflujo


2
no funcionó para mí, pero probablemente sea porque necesitaba la offsetHeightpropiedad que no es CSS.
Sebas

3

Me encontré con este desafío hoy en OSX El Capitan con Chrome v51. La página en cuestión funcionó bien en Safari. Intenté casi todas las sugerencias en esta página, ninguna funcionó bien, todas tuvieron efectos secundarios ... Terminé implementando el código a continuación, súper simple, sin efectos secundarios (todavía funciona como antes en Safari).

Solución: cambie una clase sobre el elemento problemático según sea necesario. Cada palanca forzará un nuevo dibujo. (Usé jQuery por conveniencia, pero JavaScript vainilla no debería ser un problema ...)

jQuery Class Toggle

$('.slide.force').toggleClass('force-redraw');

Clase CSS

.force-redraw::before { content: "" }

Y eso es...

NOTA: tiene que ejecutar el fragmento debajo de "Página completa" para ver el efecto.

$(window).resize(function() {
  $('.slide.force').toggleClass('force-redraw');
});
.force-redraw::before {
  content: "";
}
html,
body {
  height: 100%;
  width: 100%;
  overflow: hidden;
}
.slide-container {
  width: 100%;
  height: 100%;
  overflow-x: scroll;
  overflow-y: hidden;
  white-space: nowrap;
  padding-left: 10%;
  padding-right: 5%;
}
.slide {
  position: relative;
  display: inline-block;
  height: 30%;
  border: 1px solid green;
}
.slide-sizer {
  height: 160%;
  pointer-events: none;
  //border: 1px solid red;

}
.slide-contents {
  position: absolute;
  top: 10%;
  left: 10%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<p>
  This sample code is a simple style-based solution to maintain aspect ratio of an element based on a dynamic height.  As you increase and decrease the window height, the elements should follow and the width should follow in turn to maintain the aspect ratio.  You will notice that in Chrome on OSX (at least), the "Not Forced" element does not maintain a proper ratio.
</p>
<div class="slide-container">
  <div class="slide">
    <img class="slide-sizer" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
    <div class="slide-contents">
      Not Forced
    </div>
  </div>
  <div class="slide force">
    <img class="slide-sizer" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
    <div class="slide-contents">
      Forced
    </div>
  </div>
</div>


2
function resizeWindow(){
    var evt = document.createEvent('UIEvents');
    evt.initUIEvent('resize', true, false,window,0);
    window.dispatchEvent(evt); 
}

llame a esta función después de 500 milisegundos.


2

Si desea conservar los estilos declarados en sus hojas de estilo, es mejor usar algo como:

jQuery.fn.redraw = function() {
    this.css('display', 'none'); 
    var temp = this[0].offsetHeight;
    this.css('display', '');
    temp = this[0].offsetHeight;
};

$('.layer-to-repaint').redraw();

NB: con el último kit web / parpadeo, el almacenamiento del valor de offsetHeightes obligatorio para activar el repintado, de lo contrario, la VM (para fines de optimización) probablemente omitirá esa instrucción.

Actualización: se agregó la segunda offsetHeightlectura, es necesario evitar que el navegador ponga en cola / guarde en caché el siguiente cambio de propiedad / clase CSS con la restauración del displayvalor (de esta manera se debe procesar una transición CSS que pueda seguir)


1

Muestra de HTML:

<section id="parent">
  <article class="child"></article>
  <article class="child"></article>
</section>

Js:

  jQuery.fn.redraw = function() {
        return this.hide(0,function() {$(this).show(100);});
        // hide immediately and show with 100ms duration

    };

función de llamada:

$('article.child').redraw(); // <== mala idea

$('#parent').redraw();

también funciona en .fadeIn(1)lugar de .show(300)para mí, no empujando el elemento. Así que supongo, es el desencadenante display:noney regreso ablock
BananaAcid

0

Un enfoque que funcionó para mí en IE (no pude usar la técnica de visualización porque había una entrada que no debía perder el foco)

Funciona si tienes margen 0 (cambiar el relleno también funciona)

if(div.style.marginLeft == '0px'){
    div.style.marginLeft = '';
    div.style.marginRight = '0px';
} else {
    div.style.marginLeft = '0px';
    div.style.marginRight = '';
}

0

CSS solamente. Esto funciona para situaciones en las que se elimina o agrega un elemento secundario. En estas situaciones, los bordes y las esquinas redondeadas pueden dejar artefactos.

el:after { content: " "; }
el:before { content: " "; }

0

Mi solución para IE10 + IE11. Básicamente, lo que sucede es que agrega un DIV dentro de un elemento de ajuste que debe ser recalculado. Luego solo quítalo y listo; Funciona de maravilla :)

    _initForceBrowserRepaint: function() {
        $('#wrapper').append('<div style="width=100%" id="dummydiv"></div>');
        $('#dummydiv').width(function() { return $(this).width() - 1; }).width(function() { return $(this).width() + 1; });
        $('#dummydiv').remove();
    },

0

La mayoría de las respuestas requieren el uso de un tiempo de espera asincrónico, lo que provoca un molesto parpadeo.

Pero se me ocurrió este, que funciona sin problemas porque es sincrónico:

var p = el.parentNode,
    s = el.nextSibling;
p.removeChild(el);
p.insertBefore(el, s);

¿Seguiría funcionando algún evento adjunto a estos elementos?
Kerry Johnson

0

Esta es mi solución que funcionó para desaparecer el contenido ...

<script type = 'text/javascript'>
    var trash_div;

    setInterval(function()
    {
        if (document.body)
        {
            if (!trash_div)
                trash_div = document.createElement('div');

            document.body.appendChild(trash_div);
            document.body.removeChild(trash_div);
        }
    }, 1000 / 25); //25 fps...
</script>

0

Me encontré con un problema similar y esta simple línea de JS ayudó a solucionarlo:

document.getElementsByTagName('body')[0].focus();

En mi caso, fue un error con una extensión de Chrome que no redibujó la página después de cambiar su CSS desde dentro de la extensión.


44
Esto rompe la accesibilidad del teclado.
Pangamma

0

Quería devolver todos los estados al estado anterior (sin recargar), incluidos los elementos agregados por jquery. La implementación anterior no funcionará. e hice lo siguiente.

// Set initial HTML description
var defaultHTML;
function DefaultSave() {
  defaultHTML = document.body.innerHTML;
}
// Restore HTML description to initial state
function HTMLRestore() {
  document.body.innerHTML = defaultHTML;
}



DefaultSave()
<input type="button" value="Restore" onclick="HTMLRestore()">

0

Tuve una lista de componentes de reacción que cuando se desplazó, luego abrió otra página, luego, al regresar, la lista no se procesó en Safari iOS hasta que se desplazó la página. Entonces esta es la solución.

    componentDidMount() {
        setTimeout(() => {
            window.scrollBy(0, 0);
        }, 300);
    }

0

Debajo de css funciona para mí en IE 11 y Edge, no se necesita JS. scaleY(1)hace el truco aquí. Parece la solución más simple.

.box {
    max-height: 360px;
    transition: all 0.3s ease-out;
    transform: scaleY(1);
}
.box.collapse {
    max-height: 0;
}

0

Me ayudó

domNodeToRerender.style.opacity = 0.99;
setTimeout(() => { domNodeToRerender.style.opacity = '' }, 0);

0

2020: más ligero y más fuerte

Las soluciones anteriores ya no funcionan para mí.

Supongo que los navegadores optimizan el proceso de dibujo al detectar más y más cambios "inútiles".

Esta solución hace que el navegador dibuje un clon para reemplazar el elemento original. Funciona y probablemente sea más sostenible:

const element = document.querySelector('selector');
if (element ) {
  const clone = element.cloneNode(true);
  element.replaceWith(clone);
}

probado en Chrome 80 / Edge 80 / Firefox 75

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.