¿Cómo puedo obligar a WebKit a volver a dibujar / repintar para propagar los cambios de estilo?


247

Tengo un JavaScript trivial para efectuar un cambio de estilo:

sel = document.getElementById('my_id');
sel.className = sel.className.replace(/item-[1-9]-selected/,'item-1-selected');
return false;

Esto funciona bien con las últimas versiones de FF, Opera e IE, pero falla en las últimas versiones de Chrome y Safari.

Afecta a dos descendientes, que resultan ser hermanos. El primer hermano se actualiza, pero el segundo no. Un elemento secundario del segundo elemento también tiene foco y contiene la etiqueta <a> que contiene el código anterior en un atributo onclick .

En la ventana "Herramientas para desarrolladores" de Chrome, si empujo (por ejemplo, desmarco y verifico) cualquier atributo de cualquier elemento, el segundo hermano se actualiza al estilo correcto.

¿Existe una solución para "empujar" WebKit de manera fácil y programática para que haga lo correcto?


Creo que estoy experimentando este problema en Android 4.2.2 (Samsung I9500) al envolver mi aplicación de lienzo en un WebView. ¡Ridículo!
Josh

3
what-forces-layout.md un muy buen lugar de lectura
vsync

Respuestas:


312

Encontré algunas sugerencias complicadas y muchas simples que no funcionaron, pero un comentario de Vasil Dinkov a una de ellas me proporcionó una solución simple para forzar un nuevo dibujo / repintado que funciona bien:

sel.style.display='none';
sel.offsetHeight; // no need to store this anywhere, the reference is enough
sel.style.display='';

Dejaré que alguien más comente si funciona para otros estilos que no sean "bloque".

Gracias Vasil!


32
Para evitar el parpadeo, puede intentarlo 'inline-block', 'table'o en 'run-in'lugar de hacerlo 'none', pero esto puede tener efectos secundarios. Además, un tiempo de espera de 0 desencadena un reflujo al igual que las consultas offsetHeight:sel.style.display = 'run-in'; setTimeout(function () { sel.style.display = 'block'; }, 0);
user123444555621

15
Esta respuesta sigue siendo útil 2 años después. Solo lo utilicé para solucionar un problema solo de Chrome donde las sombras de los cuadros CSS permanecían en la pantalla después de cambiar el tamaño del navegador de ciertas maneras. ¡Gracias!
rkulla

55
@danorton Respondiste en 2010. Es 2013 y el error todavía está presente. Gracias. Por cierto, ¿alguien sabe si hay algún problema registrado en webkit tracker?
RaphaelDDL

13
PD .: para mí, la solución anterior ya no funcionaba. En cambio, usé esto (jQuery): sel.hide (). Show (0); Fuente: stackoverflow.com/questions/8840580/…
ProblemsOfSumit

77
Todo lo que necesita hacer es leer una propiedad de tamaño, no necesita cambiarla de un lado a otro.
Andrew Bullock

93

Recientemente nos encontramos con esto y descubrimos que la promoción del elemento afectado a una capa compuesta con translateZ en CSS 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. Sobre todo porque la respuesta aceptada casi con certeza causa un reflujo y repintado , no solo un repintado.

Webkit y Blink han estado trabajando arduamente en el rendimiento del renderizado, y este tipo de fallas son el desafortunado efecto secundario de las optimizaciones que apuntan a reducir flujos y pinturas innecesarios. CSS cambiará u otra especificación posterior será la solución futura, muy probablemente.

Hay otras formas de lograr una capa compuesta, pero esta es la más común.


1
¡Funcionó para mí cuando uso el carrusel angular !
gustavohenke

1
Se solucionó mi problema por el que se perdía el radio del borde en un elemento dentro de un panel deslizante
Timo

1
¡Funciona también para proyectos de Córdoba!
Quispie

1
Trabajó para mí para una pinza de línea de web que no se actualizó
Adam Marshall

1
Me funcionó en ipad (6.0) con -webkit-transform: translate (-50%, -50%).
Lain

51

La solución de Danorton no funcionó para mí. Tuve algunos problemas realmente extraños donde webkit no dibujaba algunos elementos; donde el texto en las entradas no se actualizó hasta onblur; y cambiar className no daría lugar a un redibujo.

Descubrí accidentalmente que mi solución fue agregar un elemento de estilo vacío al cuerpo, después del guión.

<body>
...
<script>doSomethingThatWebkitWillMessUp();</script>
<style></style>
...

Eso lo arregló. ¿Qué tan raro es eso? Espero que esto sea útil para alguien.


3
Votaría 1,000,000 de veces si pudiera. Esta es la única forma en que podría hacer que Chrome repinte un estúpido div que estaba arrastrando. Por cierto, no tiene que agregar un elemento de estilo (al menos no lo hice), ningún elemento funcionará. Hice un div y le di una identificación para poder eliminar y luego agregar el elemento en cada paso, para no llenar el DOM con etiquetas inútiles.
hobberwickey

66
acabo de usar esto mezclado con el comentario de Pumbaa80 en otra respuesta. La solución con la que terminé fuevar div = $('<div>').appendTo(element); setTimeout(function(){ div.remove(); }, 0);
bendman

33
¡Esta sola línea está funcionando muy bien para mí! $('<style></style>').appendTo($(document.body)).remove();
pdelanauze

1
La respuesta de @pdelanauze funciona perfectamente. Tengo un problema de volver a dibujar cantar css3 traducir y transformar en ios.
Marcel Falliere

11
Sólo saben que esto obliga a un repintado de toda la página, mientras que la mayoría de las veces sólo desea volver a pintar una determinada sección de la página ...
Ryan Wheale

33

Como el disparador display + offset no funcionó para mí, encontré una solución aquí:

http://mir.aculo.us/2009/09/25/force-redraw-dom-technique-for-webkit-based-browsers/

es decir

element.style.webkitTransform = 'scale(1)';

3
Usé esto en un truco que estaba intentando, pero en lugar de eso scale(1), me lo asigné a sí mismo como element.style.webkitTransform = element.style.webkitTransform. ¡La razón de esto es que establecerlo en el primero estaba distorsionando ligeramente la página para absoluteelementos posicionados correctamente!
bPratik

Esto funcionó para mí y no tuve problemas para parpadear o restablecer la posición de desplazamiento que displaytenía la solución.
davidtbernal

Tenía una aplicación Cordova que usaba mucho SVG dinámico. Funcionó bien en todas partes, excepto iOS7, donde simplemente no mostraría los elementos SVG en el inicio. Se agregó un simple $ ('cuerpo') [0] .style.webkitTransform = 'scale (1)'; al código de inicialización y el problema desapareció!
Herc

Esto no funciona en este momento, en los nuevos Safari e iOS.
Setholopolus

@setholopolus, ¿qué versoin (s) serían?
EricG

23

Estaba sufriendo el mismo problema. La corrección de 'pantalla de alternancia' de danorton funcionó para mí cuando se agregó a la función de paso de mi animación, pero estaba preocupado por el rendimiento y busqué otras opciones.

En mi circunstancia, el elemento que no se estaba repintando estaba dentro de un elemento de posición absoluta que, en ese momento, no tenía un índice z. Agregar un índice z a este elemento cambió el comportamiento de Chrome y los repintes ocurrieron como se esperaba -> las animaciones se suavizaron.

Dudo que esto sea una panacea, imagino que depende de por qué Chrome ha elegido no volver a dibujar el elemento, pero estoy publicando esta solución específica aquí en la ayuda que espera que alguien.

Saludos, Rob

tl; dr >> Intente agregar un índice z al elemento o uno de sus padres.


8
Brillante. Esto es increíble y solucionó el problema para mí. A +++ volvería a indexar z.
trisweb

No funcionó en mi situación tratando con barras de desplazamiento y transformaciones.
Ricky Boyce

16

Los siguientes trabajos. Solo debe configurarse una vez en CSS puro. Y funciona de manera más confiable que una función JS. El rendimiento parece no verse afectado.

@-webkit-keyframes androidBugfix {from { padding: 0; } to { padding: 0; }}
body { -webkit-animation: androidBugfix infinite 1s; }

Parece que también está funcionando para mí. Usé esto para solucionar un problema de renderizado con Mobile Safari
Chris

Esto soluciona mi problema, obliga al safari a retransmitirse. Sin embargo, usé en zoomlugar de relleno:@-webkit-keyframes safariBugFix {from { zoom: 100%; } to { zoom: 99.99999%; }}
Ahmed Musallam

13

Por alguna razón no pude obtener la respuesta de danorton para trabajar, pude ver lo que se suponía que debía hacer, así que modifiqué un poco esto:

$('#foo').css('display', 'none').height();
$('#foo').css('display', 'block');

Y funcionó para mí.


77
Intenté todo por encima de esto, pero solo este funcionó para mí. WTF webkit! ¡Esto no es informática! ¡Esto es magia negra!
Charlie Martin

7

Vine aquí porque necesitaba volver a dibujar las barras de desplazamiento en Chrome después de cambiar su CSS.

Si alguien tiene el mismo problema, lo resolví llamando a esta función:

//Hack to force scroll redraw
function scrollReDraw() {
    $('body').css('overflow', 'hidden').height();
    $('body').css('overflow', 'auto');
}

Este método no es la mejor solución, pero puede funcionar con todo, ocultar y mostrar el elemento que necesita ser redibujado puede resolver todos los problemas.

Aquí está el violín donde lo usé: http://jsfiddle.net/promatik/wZwJz/18/


1
¡Excelente! ¡También estaba tratando de animar las barras de desplazamiento y eso es lo que necesitaba! Por cierto, mejor uso de desbordamiento: superposición para barras de desplazamiento animadas
ekalchev

6

Me encontré con esto hoy: Element.redraw () para prototype.js

Utilizando:

Element.addMethods({
  redraw: function(element){
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    (function(){n.parentNode.removeChild(n)}).defer();
    return element;
  }
});

Sin embargo, a veces he notado que debe llamar a redraw () en el elemento problemático directamente. A veces, volver a dibujar el elemento padre no resolverá el problema que está experimentando el niño.

Buen artículo sobre la forma en que los navegadores procesan elementos: Renderizado: repintado, reflujo / retransmisión, restyle


1
appendChildes un método DOM, no un método jQuery.
ChrisW

4

Tuve este problema con un número de divs que se insertaron en otro div con position: absolute, los div insertados no tenían atributo de posición. Cuando cambié esto position:relative, funcionó bien. (Fue realmente difícil determinar el problema)

En mi caso los elementos fueron insertados por Angular con ng-repeat.


1
¡Gracias! Esto funcionó para mi problema también, y es mucho más liviano que muchas de las soluciones de JavaScript anteriores.
Dsyko

4

No puedo creer que esto siga siendo un problema en 2014. Acabo de tener este problema al actualizar un cuadro de subtítulos de posición fija en la parte inferior izquierda de la página mientras se desplazaba, el subtítulo se 'fantasma' en la pantalla. Después de probar todo lo anterior sin éxito, noté que muchas cosas eran lentas / causaban problemas debido a la creación de retransmisiones DOM muy cortas, etc., lo que provocaba un desplazamiento de sensación algo poco natural, etc.

Terminé haciendo una posición fija, div de tamaño completo pointer-events: noney aplicando la respuesta de danorton a ese elemento, lo que parece forzar un redibujo en toda la pantalla sin interferir con el DOM.

HTML:

<div id="redraw-fix"></div>

CSS:

div#redraw-fix {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 25;
    pointer-events: none;
    display: block;
}

JS:

sel = document.getElementById('redraw-fix');
sel.style.display='none';
sel.offsetHeight; // no need to store this anywhere, the reference is enough
sel.style.display='block';

No puedo agradecerles lo suficiente por su solución, que es la única que funcionó para mí. Sí, es 2017 y el problema persiste. Mi problema estaba relacionado con una página de lenguaje RTL que se dejaría en blanco si se actualizaba manualmente en dispositivos móviles Android. Las reglas z-indexy pointer-eventsson superfluas aquí.
Amin Mozafari

He usado la solución de Danorton antes, pero realmente estaba luchando con el destello de contenido de configurar la pantalla a ninguno. ¡Su solución funcionó muy bien para mí sin contenido flash!
Justin Noel

3

No es que esta pregunta necesite otra respuesta, pero descubrí que simplemente cambiar el color un poco obligó a volver a pintar en mi situación particular.

//Assuming black is the starting color, we tweak it by a single bit
elem.style.color = '#000001';

//Change back to black
setTimeout(function() {
    elem.style.color = '#000000';
}, 0);

El setTimeoutresultado crítico para mover el segundo cambio de estilo fuera del ciclo de eventos actual.


1
Establecer un tiempo de espera de 0 resultó útil para mí en una situación diferente pero similar. El redibujo no estaba ocurriendo antes de que la validación discreta de jquery congelara la pantalla mientras analizaba una forma grande. Pensé en comentar en caso de que alguien esté en la misma situación. Las otras soluciones no funcionaron en este escenario.
k29

2

La única solución que funciona para mí es similar a la respuesta de sowasred2012:

$('body').css('display', 'table').height();
$('body').css('display', 'block');

Tengo muchos bloques de problemas en la página, por lo que cambio la displaypropiedad del elemento raíz. Y lo uso en display: table;lugar de display: none;, porque nonerestablecerá el desplazamiento de desplazamiento.


2

Yo uso el transform: translateZ(0);método pero en algunos casos no es suficiente.

No soy fanático de agregar y eliminar una clase, así que intenté encontrar la manera de resolver esto y terminé con un nuevo truco que funciona bien:

@keyframes redraw{
    0% {opacity: 1;}
    100% {opacity: .99;}
}

// ios redraw fix
animation: redraw 1s linear infinite;

1

Como todos parecen tener sus propios problemas y soluciones, pensé que agregaría algo que funcione para mí. En Android 4.1 con Chrome actual, tratando de arrastrar un lienzo dentro de un div con desbordamiento: oculto, no pude volver a dibujar a menos que agregue un elemento al div padre (donde no haría ningún daño).

var parelt = document.getElementById("parentid");
var remElt = document.getElementById("removeMe");
var addElt = document.createElement("div");
addElt.innerHTML = " "; // Won't work if empty
addElt.id="removeMe";
if (remElt) {
    parelt.replaceChild(addElt, remElt);
} else {
    parelt.appendChild(addElt);
}

Sin parpadeo de pantalla o actualización real, y limpiando después de mí. No hay variables globales o de ámbito de clase, solo locales. No parece dañar nada en Mobile Safari / iPad o navegadores de escritorio.


1

Estoy trabajando en la aplicación iónica html5, en pocas pantallas he absolutecolocado el elemento, cuando me desplazo hacia arriba o hacia abajo en dispositivos IOS (iPhone 4,5,6, 6+) tuve un error de repintado.

Intenté muchas soluciones, ninguna de ellas funcionaba, excepto que esta resolvió mi problema.

He usado la clase CSS .fixRepainten esos elementos de posiciones absolutas

.fixRepaint{
    transform: translateZ(0);
}

Esto ha solucionado mi problema, puede ser útil para alguien


1
Opss como no he visto eso. @morewry Gracias por señalarnos
Anjum ...

0

Esto está bien para JS

sel.style.display='none';
sel.offsetHeight; // no need to store this anywhere, the reference is enough
sel.style.display='block';

Pero en Jquery, y particularmente cuando solo puede usar $ (document) .ready y no puede vincularse a un evento .load de un objeto por alguna razón en particular, lo siguiente funcionará.

Debe obtener el contenedor OUTER (MOST) de los objetos / divs y luego eliminar todo su contenido en una variable, luego volver a agregarlo. Hará visibles TODOS los cambios realizados dentro del contenedor externo.

$(document).ready(function(){
    applyStyling(object);
    var node = $("div#body div.centerContainer form div.centerHorizontal").parent().parent();
    var content = node.html();
    node.html("");
    node.html(content);
}

0

Este método me ha resultado útil al trabajar con transiciones.

$element[0].style.display = 'table'; 
$element[0].offsetWidth; // force reflow
$element.one($.support.transition.end, function () { 
    $element[0].style.display = 'block'; 
});

0

El truco "display / offsetHeight" no funcionó en mi caso, al menos cuando se aplicó al elemento que se estaba animando.

Tenía un menú desplegable que estaba siendo abierto / cerrado sobre el contenido de la página. los artefactos se dejaron en el contenido de la página después de que se cerró el menú (solo en los navegadores webkit). la única forma en que funcionó el truco "display / offsetHeight" es si lo apliqué al cuerpo, lo que parece desagradable.

Sin embargo, encontré otra solución:

  1. antes de que el elemento comience a animarse, agregue una clase que defina "-webkit-backface-visibilidad: oculto;" en el elemento (también podría usar el estilo en línea, supongo)
  2. cuando termine de animar, elimine la clase (o estilo)

Esto sigue siendo bastante hacky (usa una propiedad CSS3 para forzar la representación del hardware), pero al menos solo afecta el elemento en cuestión, y funcionó para mí tanto en Safari como en Chrome en PC y Mac.


0

Esto parece relacionado con esto: estilo jQuery no se aplica en Safari

La solución sugerida en la primera respuesta me ha funcionado bien en estos escenarios, a saber: aplicar y eliminar una clase ficticia al cuerpo después de realizar los cambios de estilo:

$('body').addClass('dummyclass').removeClass('dummyclass');

Esto obliga al safari a volver a dibujar.


Para que esto funcione en Chrome tuve que agregar: $ ('body'). AddClass ('dummyclass'). Delay (0) .removeClass ('dummyclass');
mbokil

0

las sugerencias anteriores no funcionaron para mí. pero el de abajo sí.

Quiere cambiar el texto dentro del ancla dinámicamente. La palabra "Buscar". Creó una etiqueta interna "fuente" con un atributo de identificación. Gestioné los contenidos usando javascript (abajo)

Buscar

contenido del guión:

    var searchText = "Search";
    var editSearchText = "Edit Search";
    var currentSearchText = searchText;

    function doSearch() {
        if (currentSearchText == searchText) {
            $('#pSearch').panel('close');
            currentSearchText = editSearchText;
        } else if (currentSearchText == editSearchText) {
            $('#pSearch').panel('open');
            currentSearchText = searchText;
        }
        $('#searchtxt').text(currentSearchText);
    }

0

Estaba teniendo un problema con un SVG que estaba desapareciendo en Chrome para Android cuando se cambió la orientación en ciertas circunstancias. El siguiente código no lo reproduce, pero es la configuración que teníamos.

body {
  font-family: tahoma, sans-serif;
  font-size: 12px;
  margin: 10px;
}
article {
  display: flex;
}
aside {
  flex: 0 1 10px;
  margin-right: 10px;
  min-width: 10px;
  position: relative;
}
svg {
  bottom: 0;
  left: 0;
  position: absolute;
  right: 0;
  top: 0;
}
.backgroundStop1 {
  stop-color: #5bb79e;
}
.backgroundStop2 {
  stop-color: #ddcb3f;
}
.backgroundStop3 {
  stop-color: #cf6b19;
}
<article>
  <aside>
    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="100%" width="100%">
      <defs>
        <linearGradient id="IndicatorColourPattern" x1="0" x2="0" y1="0" y2="1">
          <stop class="backgroundStop1" offset="0%"></stop>
          <stop class="backgroundStop2" offset="50%"></stop>
          <stop class="backgroundStop3" offset="100%"></stop>
        </linearGradient>
      </defs>
      <rect x="0" y="0" rx="5" ry="5" width="100%" height="100%" fill="url(#IndicatorColourPattern)"></rect>
    </svg>
  </aside>
  <section>
    <p>Donec et eros nibh. Nullam porta, elit ut sagittis pulvinar, lacus augue lobortis mauris, sed sollicitudin elit orci non massa. Proin condimentum in nibh sed vestibulum. Donec accumsan fringilla est, porttitor vestibulum dolor ornare id. Sed elementum
      urna sollicitudin commodo ultricies. Curabitur tristique orci et ligula interdum, eu condimentum metus eleifend. Nam libero augue, pharetra at maximus in, pellentesque imperdiet orci.</p>
    <p>Fusce commodo ullamcorper ullamcorper. Etiam eget pellentesque quam, id sodales erat. Vestibulum risus magna, efficitur sed nisl et, rutrum consectetur odio. Sed at lorem non ligula consequat tempus vel nec risus.</p>
  </section>
</article>

Día y medio después, después de pinchar y pinchar y no estar contento con las soluciones hacky que se ofrecen aquí, descubrí que el problema fue causado por el hecho de que parecía mantener el elemento en la memoria mientras dibujaba uno nuevo. La solución fue hacer que la ID del linealGradient en el SVG sea única, a pesar de que solo se usó una vez por página.

Esto se puede lograr de muchas maneras diferentes, pero para nuestra aplicación angular utilizamos la función lodash uniqueId para agregar una variable al alcance:

Directiva angular (JS):

scope.indicatorColourPatternId = _.uniqueId('IndicatorColourPattern');

Actualizaciones HTML:

Línea 5: <linearGradient ng-attr-id="{{indicatorColourPatternId}}" x1="0" x2="0" y1="0" y2="1">

Línea 11: <rect x="0" y="0" rx="5" ry="5" width="100%" height="100%" ng-attr-fill="url(#{{indicatorColourPatternId}})"/>

Espero que esta respuesta le ahorre a alguien más un día para destrozar su teclado.


0

Recomendaría una forma menos hackea y más formal de forzar un reflujo: use forceDOMReflowJS . En su caso, su código se vería de la siguiente manera.

sel = document.getElementById('my_id');
forceReflowJS( sel );
return false;

0

Descubrí que solo agregar un estilo de contenido al elemento lo obligaba a volver a pintar, este debería ser un valor diferente cada vez que desea volver a dibujar y no necesita estar en un pseudo elemento.

.selector {
    content: '1'
}

0

Intenté una respuesta más irónica pero no me funciona. Tuve problemas para tener el mismo ancho de cliente con safari en comparación con otros navegadores y este código resolvió el problema:

var get_safe_value = function(elm,callback){
    var sty = elm.style
    sty.transform = "translateZ(1px)";
    var ret = callback(elm)//you can get here the value you want
    sty.transform = "";
    return ret
}
// for safari to have the good clientWidth
var $fBody = document.body //the element you need to fix
var clientW = get_safe_value($fBody,function(elm){return $fBody.clientWidth})

Es realmente extraño porque si intento nuevamente obtener el ancho de cliente después de get_safe_value, obtengo un mal valor con safari, el getter debe estar entre sty.transform = "translateZ(1px)"; ysty.transform = "";

La otra solución que funciona definitivamente es

var $fBody = document.body //the element you need to fix
$fBody.style.display = 'none';
var temp = $.body.offsetHeight;
$fBody.style.display = ""
temp = $.body.offsetHeight;

var clientW = $fBody.clientWidth

El problema es que pierdes el foco y los estados de desplazamiento.


0

Esto forzará el repintado mientras evita el parpadeo, los ajustes de elementos existentes y cualquier problema de diseño ...

function forceRepaint() {
    requestAnimationFrame(()=>{
      const e=document.createElement('DIV');
      e.style='position:fixed;top:0;left:0;bottom:0;right:0;background:#80808001;\
               pointer-events:none;z-index:9999999';
      document.body.appendChild(e);
      requestAnimationFrame(()=>e.remove());  
    });
}

-1

Una solución simple con jquery:

$el.html($el.html());

o

element.innerHTML = element.innerHTML;

Tenía un SVG que no se mostraba cuando se agregaba al html.

Esto se puede agregar después de que los elementos svg estén en la pantalla.

La mejor solución es usar:

document.createElementNS('http://www.w3.org/2000/svg', 'svg');

y con jQuery:

$(svgDiv).append($(document.createElementNS('http://www.w3.org/2000/svg', 'g'));

Esto se representará correctamente en Chrome.


El problema con este enfoque es que pierde todos los controladores de eventos que ha registrado en el objeto o sus descendientes.
Haqa
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.