Принудительная перерисовка / обновление DOM на Chrome/Mac

Время от времени, Chrome будет отображать совершенно корректный HTML/CSS неправильно или нет. Заходя через инспектора DOM, часто достаточно, чтобы он понял ошибку своих путей и правильно перерисовал, так что вполне вероятно, что разметка хорошая. Это происходит достаточно часто (и, как и ожидалось, достаточно) в проекте, над которым я работаю, в котором я поместил код, чтобы вызвать перерисовку в определенных обстоятельствах.

Это работает в большинстве комбинаций браузер / операционная система:

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

Например, настройте неиспользуемое свойство CSS, затем запросите информацию, которая вызывает перерисовку, затем отмените свойство. К сожалению, яркая команда Chrome для Mac, похоже, нашла способ получить это offsetHeight без перерисовки. Таким образом убивая полезного в противном случае взломать.

Пока что лучшее, что я придумал, чтобы получить такой же эффект на Chrome/Mac, это уродство:

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

Например, на самом деле заставьте элемент немного подскочить, затем охладите секунду и отскочите назад. Хуже того, если вы сократите этот тайм-аут ниже 500 мс (туда, где он будет менее заметен), он часто не будет иметь желаемого эффекта, так как браузер не дойдет до перерисовки, пока не вернется в исходное состояние.

Кто-нибудь хочет предложить лучшую версию этого хака для перерисовки / обновления (предпочтительно на основе первого примера выше), который работает на Chrome/Mac?

25 ответов

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

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

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

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

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
}

РЕДАКТИРОВАТЬ: В ответ на Шиме Видас то, что мы достигаем здесь, будет вынужденным рефлоу. Вы можете узнать больше от самого мастера http://paulirish.com/2011/dom-html5-css3-performance/

Ни один из приведенных выше ответов не помог мне. Я заметил, что изменение размера моего окна вызвало перерисовку. Так что это сделал это для меня:

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

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

.willnotrender { 
   transform: translateZ(0); 
}

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

Это решение без таймаутов! Реальная сила перерисована! Для Android и iOS.

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

Это работает для меня. Престижность иди сюда.

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

$(el).redraw();

Если скрыть элемент, а затем снова показать его в пределах setTimeout, равного 0, произойдет перерисовка.

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

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

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

2020: легче и сильнее

Предыдущие решения для меня больше не работают.

Думаю, браузеры оптимизируют процесс рисования, обнаруживая все больше и больше "бесполезных" изменений.

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

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

протестировано на Chrome 80 / Edge 80 / Firefox 75

Вызов window.getComputedStyle() следует форсировать

Я столкнулся с этой проблемой сегодня в OSX El Capitan с Chrome v51. Рассматриваемая страница отлично работала в Safari. Я попробовал почти все предложения на этой странице - ни одно из них не сработало - у всех были побочные эффекты... В итоге я реализовал приведенный ниже код - очень простой - без побочных эффектов (все еще работает, как и раньше в Safari).

Решение. При необходимости переключите класс на проблемный элемент. Каждое переключение вызовет перерисовку. (Я использовал jQuery для удобства, но ванильный JavaScript не должен быть проблемой...)

JQuery Class Toggle

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

Класс CSS

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

И это все...

ПРИМЕЧАНИЕ. Чтобы увидеть эффект, необходимо запустить фрагмент ниже "Полная страница".

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

Если вы хотите сохранить стили, объявленные в ваших таблицах стилей, лучше использовать что-то вроде:

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: с последним webkit/blink, сохраняя значение offsetHeight является обязательным для запуска перерисовки, в противном случае виртуальная машина (в целях оптимизации), вероятно, пропустит эту инструкцию.

Обновление: добавлено второе offsetHeight читая, необходимо запретить браузеру ставить в очередь / кэшировать следующее изменение свойства / класса CSS с восстановлением display значение (таким образом, должен отображаться переход CSS, который может следовать)

Это помогло мне

domNodeToRerender.style.opacity = 0.99;
setTimeout(() => { domNodeToRerender.style.opacity = '' }, 0);
function resizeWindow(){
    var evt = document.createEvent('UIEvents');
    evt.initUIEvent('resize', true, false,window,0);
    window.dispatchEvent(evt); 
}

вызовите эту функцию через 500 миллисекунд.

эта работа для меня через 20 мс:

      const forceRedraw = elem => {
  const t = elem.ownerDocument.createTextNode(' ') ;
  elem.appendChild(t) ;
  setTimeout(() => { elem.removeChild(t) }, 0) ;
}

Образец 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

    };

функция вызова:

$('article.child').redraw(); //<== плохая идея

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

Только CSS Это работает в ситуациях, когда дочерний элемент удален или добавлен. В этих ситуациях границы и закругленные углы могут оставить артефакты.

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

Ниже css работает для меня в IE 11 и Edge, JS не требуется.scaleY(1)здесь главное. Кажется, самое простое решение.

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

Я хотел вернуть все состояния в предыдущее состояние (без перезагрузки), включая элементы, добавленные jquery. Вышеуказанная реализация не сработает. и я сделал следующее.

// 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()">

Октябрь 2020 года, проблема все еще сохраняется с Chrome 85.

Решение morewry по использованию transform:translateZ(0) работает, на самом деле работает любое преобразование, включая translate(0,0) и scale(1), но если вы должны снова обновить элемент, тогда трюк состоит в том, чтобы переключить преобразование и лучший способ - удалить его напрямую, после одного кадра, с помощью requestAnimationFrame (всегда следует избегать setTimeout, потому что он будет медленнее и может вызвать сбои).

Итак, обновим один элемент:

    function refresh_element(node) {
        // you can use scale(1) or translate(0, 0), etc
        node.style.setProperty('transform', 'translateZ(0)');
        // this will remove the property 1 frame later
        requestAnimationFrame(() => {
            node.style.removeProperty('transform');
        });
    }

Подход, который работал для меня в IE (я не мог использовать технику отображения, потому что был ввод, который не должен терять фокус)

Работает, если у вас 0 полей (смена отступов тоже работает)

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

Это мое решение, которое работало для исчезновения контента...

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

Большинство ответов требуют использования асинхронного тайм-аута, который вызывает раздражающее мигание.

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

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

Мое исправление для IE10 + IE11. По сути, происходит добавление DIV в элемент-обертку, который необходимо пересчитать. Тогда просто удали его и вуаля; работает как шарм:)

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

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

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

В моем случае это была ошибка с расширением Chrome, которое не перерисовывало страницу после изменения ее CSS изнутри расширения.

У меня был список реагирующих компонентов, который при прокрутке открывал другую страницу, затем при возврате список не отображался в Safari iOS до прокрутки страницы. Так что это исправление.

    componentDidMount() {
        setTimeout(() => {
            window.scrollBy(0, 0);
        }, 300);
    }
Другие вопросы по тегам