web-dev-qa-db-fra.com

Comment puis-je forcer WebKit à redessiner/repeindre pour propager les changements de style?

J'ai du JavaScript trivial pour effectuer un changement de style:

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

Cela fonctionne bien avec les dernières versions de FF, Opera et IE, mais échoue avec les dernières versions de Chrome et Safari.

Il affecte deux descendants, qui se trouvent être des frères et soeurs. Le premier frère est mis à jour, mais pas le second. Un enfant du deuxième élément a également le focus et contient la balise <a> qui contient le code ci-dessus dans un attribut onclick .

Dans la fenêtre «Outils de développement» de Chrome, si je modifie (par exemple, décoche et contrôle) l'attribut any de l'élément any, le second frère met à jour le style correct.

Existe-t-il une solution de contournement pour que WebKit soit facilement et par programme, permettant de faire le bon choix?

229
danorton

J’ai trouvé des suggestions compliquées et de nombreuses suggestions simples qui n’ont pas fonctionné, mais un commentaire de l'une d'elles par Vasil Dinkov a fourni une solution simple pour forcer un redessinage/repeinte qui fonctionne parfaitement:

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

Je laisserai quelqu'un d'autre commenter si cela fonctionne pour des styles autres que "block".

Merci Vasil!

292
danorton

Nous avons récemment rencontré ce problème et découvert que la promotion de l'élément affecté en couche composite with translateZ dans CSS corrigeait le problème sans recourir à JavaScript.

.willnotrender { 
   transform: translateZ(0); 
}

Comme ces problèmes de peinture apparaissent principalement dans Webkit/Blink, et que ce correctif vise principalement Webkit/Blink, il est préférable dans certains cas. Surtout que la réponse acceptée provoque presque certainement un reflow et un repaint , pas juste un repeint.

Webkit et Blink travaillent sans relâche sur les performances de rendu, et ces problèmes sont l’effet secondaire malheureux des optimisations visant à réduire les coulées et les peintures inutiles. CSS changera ou une autre spécification sera la solution future.

Il existe d'autres moyens de réaliser une couche composite, mais c'est la plus courante.

80
morewry

la solution danorton n'a pas fonctionné pour moi. J'ai eu des problèmes vraiment étranges où webkit ne dessinait pas du tout d'éléments; où le texte dans les entrées n'a pas été mis à jour jusqu'à onblur; et le changement de className n'entraînerait pas de retrait.

Ma solution, j’ai découvert par hasard, était d’ajouter un élément de style vide au corps, après le script.

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

Cela l'a corrigé. Comment est-ce bizarre? J'espère que c'est utile pour quelqu'un.

51
Gerben

Comme le déclencheur affichage + décalage ne fonctionnait pas pour moi, j'ai trouvé une solution ici:

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

c'est à dire.

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

Je souffrais du même problème. Le correctif de «basculement d'affichage» de danorton a fonctionné pour moi lorsqu'il a été ajouté à la fonction d'incrémentation de mon animation, mais je m'inquiétais de la performance et j'ai cherché d'autres options.

Dans mon cas, l'élément qui n'a pas été repeint était dans un élément de position absolue qui n'avait pas, à l'époque, d'indice z. L'ajout d'un z-index à cet élément a modifié le comportement de Chrome et les repeints se sont déroulés comme prévu -> les animations sont devenues plus fluides.

Je doute que ce soit une panacée. J'imagine que cela dépend pourquoi Chrome a choisi de ne pas redessiner l'élément, mais je publie cette solution spécifique ici dans l'aide qu'elle espère obtenir de quelqu'un.

A bientôt, Rob

tl; dr >> Essayez d’ajouter un z-index à l’élément ou à un parent de celui-ci.

22
Rob Murphy

Les travaux suivants. Il ne doit être défini qu'une fois en CSS pur. Et cela fonctionne de manière plus fiable qu'une fonction JS. La performance ne semble pas affectée.

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

Pour une raison quelconque, je ne pouvais pas obtenir la réponse de danorton au travail, je pouvais voir ce que c'était censé faire, alors je l'ai un peu modifié pour ceci:

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

et cela a fonctionné pour moi.

12
sowasred2012

Je suis venu ici parce que je devais redessiner les barres de défilement dans Chrome après avoir changé son css.

Si quelqu'un a le même problème, je l'ai résolu en appelant cette fonction:

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

Cette méthode n’est pas la meilleure solution, mais elle peut fonctionner avec tout, cacher et montrer l’élément à redessiner peut résoudre tous les problèmes.

Voici le violon où je l'ai utilisé: http://jsfiddle.net/promatik/wZwJz/18/

6
Toni Almeida

Je suis tombé sur ceci aujourd'hui: Element.redraw () for prototype.js

En utilisant: 

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

Cependant, j'ai parfois remarqué que vous devez appeler directement redraw () sur l'élément problématique. Parfois, redessiner l'élément parent ne résoudra pas le problème rencontré par l'enfant.

Bon article sur la manière dont les navigateurs rendent les éléments: Rendu: repeindre, refondre/relooker, restyle

6
Adam Eberlin

Je n'arrive pas à croire que le problème persiste en 2014. Je venais de résoudre ce problème lors de l'actualisation d'une zone de légende à position fixe dans la partie inférieure gauche de la page lors du défilement, la légende «fantasmant» en remontant de l'écran. Après avoir essayé tout ce qui précède sans succès, j'ai remarqué que beaucoup de choses restaient lentes/posaient problème en raison de la création de relais très courts dans le DOM, etc.

J'ai fini par faire une position fixe, une taille normale avec pointer-events: none et appliquer la réponse de danorton à cet élément, ce qui semble forcer un redessin sur tout l'écran sans interférer avec le 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';
4
waffl

J'ai eu ce problème avec un certain nombre de divs qui ont été insérés dans un autre div avec position: absolute, les divs insérés n'avaient aucun attribut de position. Quand j'ai changé ceci en position:relative cela a bien fonctionné. (était vraiment difficile de cerner le problème)

Dans mon cas, les éléments ont été insérés par Angular avec ng-repeat.

4
JustGoscha

Ce n’est pas que cette question appelle une autre réponse, mais j’ai trouvé qu’un simple changement de couleur obligeait à repeindre dans mon cas particulier.

//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);

La setTimeout s'est avérée essentielle pour déplacer le deuxième changement de style en dehors de la boucle d'événements en cours.

3
TMan

La seule solution qui fonctionne pour moi est similaire à la réponse de sowasred2012:

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

J'ai beaucoup de blocs de problèmes sur la page, donc je change la propriété display de l'élément racine . Et j'utilise display: table; au lieu de display: none;, car none réinitialisera le décalage de défilement.

2
sviriden

Puisque tout le monde semble avoir ses propres problèmes et solutions, je me suis dit que j'ajouterais quelque chose qui fonctionne pour moi. Sur Android 4.1 avec Chrome actuel, essayer de faire glisser un canevas dans un div avec un débordement: masqué, je ne pouvais pas obtenir de mise à jour à moins d’ajouter un élément au div parent (où il ne ferait aucun mal).

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);
}

Pas de scintillement de l'écran ou de mise à jour réelle, et nettoyage après moi-même. Aucune variable globale ou de portée de classe, juste des variables locales. Ne semble pas blesser quoi que ce soit sur Mobile Safari/iPad ou les navigateurs de bureau.

1
Eric Ruck

Je travaille sur l'application html5 ionique. Sur quelques écrans, l'élément absolute est positionné, lorsque vous faites défiler l'écran vers le haut ou le bas IOS - appareils (iPhone 4,5,6, 6+) que j'ai eu à repeindre.

J'ai essayé beaucoup de solutions, aucune d'entre elles ne fonctionnait sauf celle-ci qui résolvait mon problème.

J'ai la classe css .fixRepaint sur ces éléments de positions absolues 

.fixRepaint{
    transform: translateZ(0);
}

Cela a résolu mon problème, cela peut être d'aider quelqu'un 

1
Anjum....

J'ai constaté que le simple fait d'ajouter un style de contenu à l'élément l'obligeait à le repeindre. Cette valeur devrait être différente chaque fois que vous souhaitez redessiner l'image et n'a pas besoin d'être sur un pseudo-élément.

.selector {
    content: '1'
}
0
Steve

J'avais un problème avec un SVG qui disparaissait sous Chrome pour Android lorsque l'orientation a été modifiée dans certaines circonstances. Le code ci-dessous ne le reproduit pas, mais correspond à la configuration que nous avions.

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>

Un jour et demi plus tard, après avoir insisté et insatisfait des solutions hacky proposées ici, j'ai découvert que le problème était dû au fait qu'il semblait conserver l'élément en mémoire tout en en dessinant un nouveau. La solution consistait à rendre l’ID du linearGradient sur le SVG unique, même s’il n’était utilisé qu’une fois par page.

Cela peut être réalisé de différentes manières, mais pour notre application angulaire, nous avons utilisé la fonction lodash uniqueId pour ajouter une variable à la portée:

Directive angulaire (JS):

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

Mises à jour HTML:

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

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

J'espère que cette réponse permettra à quelqu'un d'autre d'économiser quelques jours de frappe au clavier.

0
Jamie Barker

les suggestions ci-dessus n'ont pas fonctionné pour moi. mais celui ci-dessous fait.

Voulez-vous changer le texte à l'intérieur de l'ancre de manière dynamique. Le mot "recherche". Création d'une balise interne "police" avec un attribut id. Gérer le contenu en utilisant javascript (ci-dessous)

Chercher

contenu du script:

    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
ganesh

J'utilise la méthode transform: translateZ(0); mais dans certains cas, cela ne suffit pas.

Je ne suis pas fan de l'ajout et de la suppression d'une classe, j'ai donc essayé de trouver un moyen de résoudre ce problème et je me suis retrouvé avec un nouveau hack qui fonctionne bien:

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

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

Cela semble lié à ceci: Le style jQuery n’est pas appliqué dans Safari

La solution suggérée dans la première réponse a bien fonctionné pour moi dans ces scénarios, à savoir: appliquer et supprimer une classe fictive au corps après les modifications de style:

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

Cela oblige le safari à se redessiner.

0
steve

J'ai trouvé cette méthode utile lorsque vous travaillez avec des transitions.

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

C'est bien pour JS

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

Mais dans JQuery, et particulièrement lorsque vous ne pouvez utiliser que $ (document) .ready et ne pouvez pas vous lier à l’événement .load d’un objet pour une raison particulière, ce qui suit fonctionnera.

Vous devez obtenir le conteneur OUTER (MOST) des objets/divs, puis supprimer tout son contenu dans une variable, puis l'ajouter de nouveau . Il rendra TOUTES les modifications effectuées dans le conteneur externe.

$(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
WiR3D

J'ai essayé plusieurs réponses mais cela ne fonctionne pas pour moi ... J'ai eu du mal à avoir le même clientWidth avec Safari par rapport à d'autres navigateurs et ce code a résolu le problème:

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})

C'est vraiment étrange, car si j'essaie encore d'obtenir clientWidth après get_safe_value, j'obtiens une valeur incorrecte avec safari, le getter doit être compris entre sty.transform = "translateZ(1px)"; et sty.transform = "";

L’autre solution qui fonctionne définitivement est

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

Le problème est que vous perdez le focus et les états de défilement.

0
bormat

le hack "display/offsetHeight" ne fonctionnait pas dans mon cas, du moins lorsqu'il était appliqué à l'élément en cours d'animation.

j'avais un menu déroulant qui était ouvert/fermé sur le contenu de la page. les artefacts étaient laissés sur le contenu de la page après la fermeture du menu (uniquement dans les navigateurs Webkit). le hack "display/offsetHeight" fonctionne uniquement si je l'applique sur le corps, ce qui semble méchant.

cependant, j'ai trouvé une autre solution:

  1. avant que l'élément ne commence à animer, ajoutez une classe qui définit "-webkit-backface-visibilité: hidden;" sur l'élément (vous pouvez aussi utiliser le style en ligne, je suppose)
  2. quand c'est fini d'animer, supprimez la classe (ou le style)

c'est encore assez hacky (il utilise une propriété CSS3 pour forcer le rendu du matériel), mais au moins cela n'affecte que l'élément en question et a fonctionné pour moi à la fois sur safari et chrome sur PC et Mac.

0
tedtedson

Je recommanderais un moyen moins formel et plus formel de forcer un refoulement: utilisez forceDOMReflowJS . Dans votre cas, votre code ressemblerait à ce qui suit.

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