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

247

У меня есть несколько тривиальных 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.

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

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

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

danorton
источник
Я думаю, что я испытываю эту проблему на Android 4.2.2 (Samsung I9500), когда оборачиваю мое приложение Canvas в WebView. Смешной!
Джош
3
what-forces-layout.md очень хорошее место для чтения
vsync

Ответы:

312

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

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

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

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

danorton
источник
32
Чтобы избежать мерцания, вы можете попробовать 'inline-block', 'table'или 'run-in'вместо 'none', но это может иметь побочные эффекты. Кроме того, тайм-аут, offsetHeightsel.style.display = 'run-in'; setTimeout(function () { sel.style.display = 'block'; }, 0);
равный
15
Этот ответ все еще полезен 2 года спустя. Я просто использовал его, чтобы исправить проблему, связанную только с Chrome, когда тени CSS-блоков оставались на экране после изменения размера браузера определенным образом. Спасибо!
rkulla
5
@danorton Вы ответили в 2010 году. Это 2013 год, и ошибка все еще есть. Спасибо. Кстати, кто-нибудь знает, есть ли какая-либо проблема, зарегистрированная на трекере webkit?
RaphaelDDL
13
PS: для меня вышеупомянутое решение больше не работает. Вместо этого я использовал это (jQuery): sel.hide (). Show (0); Источник: stackoverflow.com/questions/8840580/...
ProblemsOfSumit
7
Все, что вам нужно сделать, это прочитать свойство размера, вам не нужно менять его туда-сюда.
Эндрю Баллок
93

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

.willnotrender { 
   transform: translateZ(0); 
}

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

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

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

morewry
источник
1
Работал у меня при использовании угловой-карусели !
Густавохенке
1
Исправлена ​​ошибка, из-за которой границы элемента были потеряны в элементе внутри скользящей панели
Тимо
1
Работает и для кордовых проектов!
Quispie
1
Работал на меня для -webkit-line-зажима, который не обновлялся
Адам Маршалл
1
У меня работал на ipad (6.0) с -webkit-transform: translate (-50%, -50%).
Lain
51

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

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

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

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

Gerben
источник
3
Я бы проголосовал 1000000 раз, если бы мог. Это единственный способ заставить Chrome перекрашивать глупый div, который я тащил вокруг. Кстати, вам не нужно добавлять элемент стиля (по крайней мере, я этого не делал), любой элемент будет работать. Я сделал div и дал ему идентификатор, чтобы я мог удалить, а затем добавить элемент на каждом шаге, чтобы не заполнять DOM бесполезными тегами.
hobberwickey
6
просто использовал это вместе с комментарием Pumbaa80 к другому ответу. Исправление, которое я закончил, былоvar div = $('<div>').appendTo(element); setTimeout(function(){ div.remove(); }, 0);
бендман
33
Эта единственная линия отлично работает для меня! $('<style></style>').appendTo($(document.body)).remove();
pdelanauze
1
@pdelanauze ответ работает отлично. У меня есть проблема перерисовать петь css3 перевести и трансформировать в IOS.
Марсель Фальер
11
Просто знайте, что это вызывает перерисовку всей страницы, в то время как в большинстве случаев вам нужно перекрашивать только определенный раздел страницы ...
Райан Уил
33

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

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

т.е.

element.style.webkitTransform = 'scale(1)';
EricG
источник
3
Я использовал это в попытке взломать, но вместо этого scale(1)я назначил это себе element.style.webkitTransform = element.style.webkitTransform. Причиной этого было то, что установка его в прежнее состояние слегка absoluteискажала страницу для хорошо расположенных элементов!
bPratik
Это сработало для меня, и у меня не было проблем с мерцанием или сбросом положения прокрутки, что и displayрешение.
Давидтбернал
У меня было приложение Cordova, использующее множество динамических SVG. Работал хорошо везде, кроме iOS7, где просто не отображались элементы SVG при запуске. Добавлен простой $ ('body') [0] .style.webkitTransform = 'scale (1)'; к коду инициализации и проблема исчезла!
Herc
Это не работает на данный момент, на новейших Safari и iOS.
Setholopolus
@setholopolus, какие versoin (ы) это будет?
EricG
23

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

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

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

Ура, Роб

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

Роб Мерфи
источник
8
Brilliant. Это круто и исправило проблему для меня. A +++ снова будет z-index.
trisweb
В моей ситуации не работали полосы прокрутки и преобразования.
Рикки Бойс
16

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

@-webkit-keyframes androidBugfix {from { padding: 0; } to { padding: 0; }}
body { -webkit-animation: androidBugfix infinite 1s; }
sweeds
источник
Похоже, это работает и для меня. Используйте это, чтобы исправить проблему рендеринга с Mobile Safari
Chris
Это решает мою проблему, заставляет сафари ретранслировать. Тем не менее, я использовал zoomвместо заполнения:@-webkit-keyframes safariBugFix {from { zoom: 100%; } to { zoom: 99.99999%; }}
Ахмед
13

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

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

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

sowasred2012
источник
7
Я перепробовал все выше, но у меня работал только этот. WTF webkit! Это не информатика! Это черная магия!
Чарли Мартин
7

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

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

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

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

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

Антонио Алмейда
источник
1
Большой! Я тоже пытался анимировать полосы прокрутки, и это то, что мне было нужно! Кстати лучше использовать переполнение: наложение для анимированных полос прокрутки
ekalchev
6

Я наткнулся на это сегодня: 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 () для проблемного элемента напрямую. Иногда перерисовка родительского элемента не решит проблему, с которой сталкивается ребенок.

Хорошая статья о том, как браузеры отображают элементы: Rendering: repaint, reflow / relayout, restyle

Адам Эберлин
источник
1
appendChildэто метод DOM, а не метод jQuery.
ChrisW
4

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

В моем случае элементы были вставлены с помощью Angular с ng-repeat.

JustGoscha
источник
1
Спасибо! Это сработало и для моей проблемы, и намного легче, чем многие из решений javascript выше.
Dsyko
4

Я не могу поверить, что это все еще проблема в 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';
waffl
источник
Я не могу отблагодарить вас за ваш обходной путь, который единственный, который работал на меня. Да, это 2017, и проблема продолжается. Моя проблема была связана с языковой страницей RTL, которая была бы пустой при обновлении вручную на мобильных устройствах Android. z-indexИ pointer-eventsправила здесь излишни.
Амин Мозафари
Раньше я использовал исправление Данортона, но на самом деле боролся со вспышкой контента, не устанавливая отображение на ноль. Ваше решение отлично сработало для меня без флеш-контента!
Джастин Ноэль
3

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

//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Оказался критическим для перемещения второго изменения стиля за пределами текущего цикла событий.

TMan
источник
1
Установка времени ожидания 0 оказалась полезной для меня в другой, но похожей ситуации. Перерисовка не происходила до того, как ненавязчивая проверка jquery заморозила экран, пока он анализировал большую форму. Думаю, я бы прокомментировал, если кто-то там окажется в такой же ситуации. Другие решения не работали в этом сценарии.
k29
2

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

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

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

Илья Свириденко
источник
2

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

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

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

// ios redraw fix
animation: redraw 1s linear infinite;
Гийом Готье
источник
1

Поскольку у каждого, похоже, есть свои проблемы и решения, я решил добавить кое-что, что мне подходит. В 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 или настольных браузерах.

Эрик Рак
источник
1

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

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

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

.fixRepaint{
    transform: translateZ(0);
}

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

Anjum ....
источник
1
Опсс, как я этого не видел. @morewry Спасибо за указание
Anjum ....
0

Это хорошо для 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);
}
WiR3D
источник
0

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

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

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

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

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

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

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

tedtedson
источник
0

Кажется, это связано с этим: стиль jQuery не применяется в Safari

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

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

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

Стив
источник
Чтобы это работало в Chrome, мне пришлось добавить: $ ('body'). AddClass ('dummyclass'). Delay (0) .removeClass ('dummyclass');
Мбокил
0

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

Хотите изменить текст внутри якоря динамически. Слово «Поиск». Создан внутренний тег «шрифт» с атрибутом 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);
    }
Ганеша
источник
0

У меня была проблема с 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}})"/>

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

Джейми Баркер
источник
0

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

sel = document.getElementById('my_id');
forceReflowJS( sel );
return false;
Джек Гиффин
источник
0

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

.selector {
    content: '1'
}
Стив
источник
0

Я пытался ответить больше, но он не работает для меня. У меня были проблемы с тем же clientWidth с сафари по сравнению с другими браузерами, и этот код решил проблему:

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

Это действительно странно, потому что если я попытаюсь снова получить clientWidth после get_safe_value, я получу неверное значение с помощью safari, получатель должен быть между sty.transform = "translateZ(1px)"; иsty.transform = "";

Другое решение, которое работает окончательно

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

Проблема в том, что вы теряете фокус и состояния прокрутки.

bormat
источник
0

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

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());  
    });
}
user2026189
источник
-1

Простое решение с помощью jquery:

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

или

element.innerHTML = element.innerHTML;

Был SVG, который не показывался, когда он был добавлен в HTML.

Это можно добавить после того, как элементы svg появятся на экране.

Лучшее решение - использовать:

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

и с помощью jQuery:

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

это будет правильно отображаться на Chrome.

Ярон Шамир
источник
Проблема этого подхода заключается в том, что вы теряете все обработчики событий, которые вы зарегистрировали для объекта или его потомков.
Хака