Как заставить WebKit перерисовать / перекрасить для распространения изменений стиля?

У меня есть несколько тривиальных JavaScript для изменения стиля:

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

Это прекрасно работает с последними версиями FF, Opera и IE, но не работает на последних версиях Chrome и Safari.

Это затрагивает двух потомков, которые, случается, являются родными братьями. Первый брат обновляется, а второй нет. Дочерний элемент второго элемента также имеет фокус и содержит тег , содержащий вышеуказанный код в атрибуте onclick.

В окне Chrome "Инструменты разработчика", если я подтолкну (например, сниму флажок и проверим) какой-либо атрибут какого- либо элемента, второй брат обновится до правильного стиля.

Есть ли обходной путь, позволяющий легко и программно "подтолкнуть" WebKit на правильные действия?

33 ответа

Решение

Я нашел несколько сложных предложений и множество простых, которые не сработали, но комментарий Васила Динкова к одному из них предоставил простое решение для принудительной перерисовки / перерисовки, которое работает просто отлично:

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

Я позволю кому-то еще прокомментировать, если это работает для стилей, отличных от "блока".

Спасибо, Василь!

Недавно мы столкнулись с этим и обнаружили, что продвижение уязвимого элемента в составной слой с translateZ в CSS решило проблему без необходимости дополнительного JavaScript.

.willnotrender { 
   transform: translateZ(0); 
}

Поскольку эти проблемы рисования в основном проявляются в Webkit/Blink, и это исправление в основном предназначено для Webkit/Blink, в некоторых случаях это предпочтительно. Тем более что принятый ответ почти наверняка вызывает перерисовку и перекрашивание, а не просто перекрашивание.

Webkit и Blink усердно работали над производительностью рендеринга, и такие глюки являются неблагоприятным побочным эффектом оптимизаций, направленных на уменьшение ненужных потоков и красок. Скорее всего, будет изменение CSS или другая последующая спецификация.

Есть и другие способы достижения составного слоя, но это наиболее распространенный.

Решение Danorton не работает для меня. У меня были некоторые действительно странные проблемы, когда webkit вообще не рисовал некоторые элементы; где текст на входах не обновлялся до onblur; и изменение className не приведет к перерисовке.

Мое решение, как я случайно обнаружил, было добавить пустой элемент стиля в тело после сценария.

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

Это исправило это. Насколько это странно? Надеюсь, что это полезно для кого-то.

Поскольку триггер display + offset мне не помог, я нашел решение здесь:

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

т.е.

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

Я страдал от той же проблемы. Исправление "переключения отображения" Данортона сработало для меня, когда его добавили к функции шага моей анимации, но я беспокоился о производительности и искал другие варианты.

В моих обстоятельствах элемент, который не перерисовывался, находился внутри абсолютно позиционного элемента, который в то время не имел z-индекса. Добавление z-индекса к этому элементу изменило поведение Chrome, и перерисовка произошла, как и ожидалось -> анимация стала плавной.

Я сомневаюсь, что это панацея, я думаю, это зависит от того, почему Chrome решил не перерисовывать элемент, но я публикую это конкретное решение здесь, чтобы помочь кому-то.

Ура, Роб

tl;dr >> Попробуйте добавить z-index к элементу или его родительскому элементу.

Следующие работы. Его нужно установить только один раз в чистом CSS. И это работает более надежно, чем функция JS. Производительность кажется неизменной.

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

По какой-то причине я не мог получить ответ Данортона на работу, я мог видеть, что он должен был делать, поэтому я немного подправил это:

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

и это сработало для меня.

Я пришел сюда, потому что мне нужно было перерисовать полосы прокрутки в Chrome после изменения его CSS.

Если у кого-то возникла такая же проблема, я решил ее, вызвав эту функцию:

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

Этот метод не является лучшим решением, но он может работать со всем, скрывая и показывая элемент, который должен быть перерисован, можно решить любую проблему.

Вот скрипка, где я ее использовал: http://jsfiddle.net/promatik/wZwJz/18/

Я наткнулся на это сегодня: Element.redraw() для prototype.js

С помощью:

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

Однако иногда я замечал, что вы должны вызывать redraw() для проблемного элемента напрямую. Иногда перерисовка родительского элемента не решит проблему, с которой сталкивается ребенок.

Хорошая статья о том, как браузеры отображают элементы: http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/

Я не могу поверить, что это все еще проблема в 2014 году. У меня только была эта проблема при обновлении поля заголовка с фиксированной позицией в левой нижней части страницы во время прокрутки, когда заголовок "появлялся" на экране. Попробовав все вышеперечисленное безуспешно, я заметил, что многие вещи были либо медленными, либо вызывали проблемы из-за создания очень коротких ретрансляций DOM и т. Д., Вызывая несколько неестественное чувство прокрутки и т. Д...

В итоге я сделал фиксированную позицию, полноразмерный div с pointer-events: none и применение ответа Данортона к этому элементу, который, кажется, вызывает перерисовку на всем экране, не влияя на 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';

У меня была эта проблема с несколькими div, которые были вставлены в другой div с position: absoluteвставленные div не имеют атрибута позиции. Когда я изменил это на position:relative это работало нормально. (было действительно трудно определить проблему)

В моем случае элементы были вставлены Angular с ng-repeat,

Я использую transform: translateZ(0); метод, но в некоторых случаях этого недостаточно.

Я не фанат добавления и удаления класса, поэтому я попытался найти способ решить эту проблему и в итоге получил новый хак, который хорошо работает:

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

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

Не то чтобы этот вопрос нуждался в другом ответе, но я обнаружил, что просто изменение цвета на один бит заставило перекрасить в моей конкретной ситуации.

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

setTimeout оказалось крайне важным перенести второе изменение стиля за пределы текущего цикла событий.

Единственное решение работает для меня похоже на ответ sowasred2012:

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

У меня много проблемных блоков на странице, поэтому я меняю display свойство корневого элемента. И я пользуюсь display: table; вместо display: none;, так как none сбросит смещение прокрутки.

Этот ответ для тех, кто борется с решением @danorton с использованием Angular 10. Это решение

      sel.style.display='none';
sel.offsetHeight;
sel.style.display='';

у меня работает только с "buildOptimizer": false в angular.jsonфайл. Похоже, оптимизатор как-то это ломает.

Поскольку у каждого, похоже, есть свои проблемы и решения, я решил добавить кое-что, что мне подходит. В Android 4.1 с текущей версией Chrome, пытаясь перетащить холст внутри div с переполнением: скрытым, я не мог получить перерисовку, если не добавил элемент в родительский div (где это не принесло бы никакого вреда).

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

Никакого мерцания экрана или реального обновления, и очистки после себя. Никаких глобальных или классовых переменных, только локальные. Похоже, что ничего не повредит на мобильных браузерах Safari/iPad или настольных ПК.

Я работаю над приложением ионной HTML5, на нескольких экранах у меня есть absolute позиционируемый элемент, при прокрутке вверх или вниз на устройствах IOS (iPhone 4,5,6, 6+) у меня была ошибка перекраски.

Пробовал много решений, ни один из них не работал, кроме этого, решить мою проблему.

Я использую класс CSS .fixRepaint на этих элементах абсолютных позиций

.fixRepaint{
    transform: translateZ(0);
}

Это исправило мою проблему, может быть, кто-то поможет

Я обнаружил, что простое добавление стиля содержимого к элементу вынуждает его перерисовываться, это должно быть другое значение каждый раз, когда вы хотите, чтобы он перерисовывался, и не должно быть в псевдоэлементе.

.selector {
    content: '1'
}

Это похоже на следующее: стиль jQuery не применяется в Safari

Решение, предложенное в первом ответе, хорошо сработало для меня в этих сценариях, а именно: применить и удалить фиктивный класс к телу после внесения изменений в стиль:

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

Это заставляет сафари перерисовать.

У меня была проблема с SVG, который исчезал в Chrome для Android, когда ориентация менялась при определенных обстоятельствах. Приведенный ниже код не воспроизводит его, но является настройкой, которую мы имели.

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>

Спустя полтора дня после того, как тыкал и подталкивал и не был доволен хакерскими решениями, предложенными здесь, я обнаружил, что проблема была в том, что он, казалось, удерживал элемент в памяти при рисовании нового. Решение состояло в том, чтобы сделать идентификатор linearGradient в SVG уникальным, даже если он использовался только один раз на страницу.

Это может быть достигнуто многими различными способами, но для нашего углового приложения мы использовали функцию lodash uniqueId, чтобы добавить переменную в область видимости:

Угловая директива (JS):

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

Обновления HTML:

Строка 5: <linearGradient ng-attr-id="{{indicatorColourPatternId}}" x1="0" x2="0" y1="0" y2="1">

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

Я надеюсь, что этот ответ сэкономит кому-то еще несколько дней, чтобы разбить их клавиатуру.

I tried morewry answer but it does not work for me. I had trouble to have the same clientWidth with safari comparing to others browsers and this code solved the problem:

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

It is really strange because if I try again to get the clientWidth after get_safe_value, I obtain a bad value with safari, the getter has to be between sty.transform = "translateZ(1px)"; а также sty.transform = "";

The other solution that works definitively is

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

The problem is that you lose focus and scroll states.

Хак "display/offsetHeight" не работал в моем случае, по крайней мере, когда он был применен к анимируемому элементу.

у меня было выпадающее меню, которое открывалось / закрывалось над содержимым страницы. артефакты оставались в содержимом страницы после закрытия меню (только в браузерах webkit). хак "display/offsetHeight" работал только в том случае, если я применил его к телу, что кажется неприятным.

Тем не менее, я нашел другое решение:

  1. перед началом анимации элемента добавьте класс, который определяет "-webkit-backface-visibility: hidden;"; на элемент (вы могли бы также использовать встроенный стиль, я думаю,)
  2. когда закончите анимацию, удалите класс (или стиль)

это все еще довольно хакерский (он использует свойство CSS3 для принудительного аппаратного рендеринга), но, по крайней мере, он влияет только на рассматриваемый элемент, и работал для меня как в Safari, так и в Chrome на ПК и Mac.

Этот код будет повторно отображать css

       document.body.style.display = 'flex';
 setTimeout(() => (document.body.style.display = ''), 0);

Установка CSS преобразования на scale(0.9999) видимо работает в новейшем хроме.

      function redraw(node){
     // Adjust the 200 as fastest as you can
     // or, change the setTimeout to requestAnimationFrame as soon as the element
     // is drawn
     setTimeout(() => (node.style.transform = "scale(0.9999)"), 200);
}

Я обнаружил, что моя проблема была решена отличным ответом @morewry , и, кроме того, will-change собственность прибыла с.

Скорее всего, будущим решением будет изменение CSS или другая последующая спецификация.

В моем случае значение will-change: transform; только был эффективен в Safari 14.

      .wont-update {
    will-change: transform;

    /* for Safari < 9.1 */
    transform: translateZ(0);
}

Я бы порекомендовал менее хакерский и более формальный способ форсирования: использовать forceDOMReflowJS. В вашем случае ваш код будет выглядеть следующим образом.

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

Это хорошо для JS

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

Но в Jquery, и особенно когда вы можете использовать только $(document).ready и не можете привязать к событию.load объекта по какой-либо конкретной причине, будет работать следующее.

Вам нужно получить OUTER(MOST) контейнер объектов /div, а затем удалить все его содержимое в переменную, а затем снова добавить его. Это сделает ВСЕ изменения, сделанные во внешнем контейнере видимыми.

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

Я нашел этот метод полезным при работе с переходами

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

Вышеупомянутые предложения не работали для меня. но ниже один делает.

Хотите изменить текст внутри якоря динамически. Слово "Поиск". Создан внутренний тег "шрифт" с атрибутом id. Управлял содержимым с помощью JavaScript (ниже)

Поиск

содержание скрипта:

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

Я также столкнулся с той же проблемой при работе с Angular 10. Я пробовал много решений, но ничего не работает. Исправление, которое сработало для меня, обрабатывает его через JS. Я просто удалил элемент из DOM и снова добавил его с помощью ngIf.

HTML:

      <div *ngIf="showElement">
  Contents of your element
</div>

JS:

      this.showElement = false;
setTimeout(() => {
  this.showElement = true;
}, 10);
Другие вопросы по тегам