Подтвердить что ты не робот

Переход CSS не выполняется при обратном вызове jQuery.load

У меня есть функция, которая загружает html в таблицу с jQuery и затем добавляет класс в одну из строк с обратным вызовом. Эта функция запускается различными событиями, управляемыми пользовательским интерфейсом на странице. У меня также есть правило перехода css, поэтому цвет должен исчезать (transition: background-color 1000ms linear). Функция выглядит следующим образом:

function load_tbody(row_id) {
    $('tbody').load("load_tbody.php", function() {
        $(row_id).addClass('green');
    });
}

После загрузки html класс успешно добавляется, а цвет строки - зеленый. Однако мое правило перехода css, похоже, игнорируется.

Когда я добавляю небольшую задержку, даже 10 мс, она отлично работает:

function load_tbody(row_id) {
    $('tbody').load("load_tbody.php", function() {
        setTimeout(function() {
            $(row_id).addClass('green');
        }, 10);
    });
}

jQuery docs для .load():

Если предоставляется "полный" обратный вызов, он выполняется после пост-обработки и вставки HTML.

Для меня это означало бы, что новые элементы были загружены в dom с существующими стилями и готовы к манипуляции. Почему переход не выполняется в первом примере, но преуспеть во втором?

Вот полнофункциональная страница примера, демонстрирующая поведение, о котором идет речь:

http://so-37035335.dev.zuma-design.com/

В приведенном выше примере ссылки jQuery версии 2.2.3 из cdn, фактическая страница, о которой идет речь, использует версию 1.7.1. Такое же поведение наблюдается в обеих версиях.

UPDATE:

После рассмотрения некоторых комментариев и ответов, предложенных ниже, я наткнулся на что-то более запутанное. Пользователь @gdyrrahitis сделал предложение, которое заставило меня сделать это:

function tbody_fade(row_id) {
    $('tbody').load("load_tbody.php", function() {
        $('tbody').fadeIn(0, function() {
          $(this).find(row_id).addClass('green');
        });
    });
}

Добавление класса внутри обратного вызова fadeIn() работает даже с длительностью 0 мс. Так что это заставило меня задаться вопросом... если элемент теоретически существует в любом случае, какой фоновый цвет делает браузер, который он имеет до того, как я добавлю этот класс. Поэтому я записываю background-color:

console.log($(row_id).css('background-color'));

И знаете ли вы что? Простое получение цвета фона фона сделало все:

function tbody_get_style(row_id) {
    $('tbody').load("load_tbody.php", function() {
        $(row_id).css('background-color');
        $(row_id).addClass('green');
    });
}

Просто добавив строку $(row_id).css('background-color');, которая, по-видимому, ничего не делает, заставляет эффект перехода работать. Вот демо:

http://so-37035335-b.dev.zuma-design.com/

Я просто ошеломлен этим. Почему это работает? Это просто добавление небольшой задержки или делает jQuery получение свойства css каким-то образом существенно влияет на состояние вновь добавленного элемента?

4b9b3361

Ответ 1

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

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

Существует еще одно исключение правила дозирования. Браузеру необходимо рассчитать ближайшее значение, если вы пытаетесь получить к нему доступ. Одним из этих значений является offsetWidth. Когда вы обращаетесь к нему, происходит перезапуск. Еще один выполняется отдельно во время фактического отображения. Опять же, у нас есть два разных значения стиля, поэтому мы можем интерполировать их во времени.

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

Предпочтительное решение может отличаться от человека к человеку, но для меня лучше всего подходит доступ offsetWidth (или getComputedStyle()). Бывают случаи, когда setTimeout запускается без повторного пересчета стилей между ними. Это редкий случай, в основном на загруженных сайтах, но это происходит. Тогда вы не получите свою анимацию. Получая доступ к любому расчетному стилю, вы вынуждаете браузер фактически вычислять его

Триггерный переход CSS на добавленный элемент

Объяснение Для последней части

Метод .css() - это удобный способ получить свойство стиля из первого совпадающего элемента, особенно в свете различных способов доступа браузеров к большинству этих свойств (метод getComputedStyle() в браузерах, основанных на стандартах, в сравнении с currentStyle и свойства runtimeStyle в Internet Explorer) и различные термины, используемые браузерами для определенных свойств.

В некотором смысле .css() является jquery-эквивалентом функции javascript getComputedStyle(), которая объясняет, почему добавление свойства css перед добавлением класса заставляло все работать

Документация JQuery.css()

// Does not animate
var $a = $('<div>')
    .addClass('box a')
    .appendTo('#wrapper');
    $a.css('opacity');
    $a.addClass('in');


// Check it not just jQuery
// does not animate
var e = document.createElement('div');
e.className = 'box e';
document.getElementById('wrapper').appendChild(e);
window.getComputedStyle(e).opacity;
e.className += ' in';
.box { 
  opacity: 0;
  -webkit-transition: all 2s;
     -moz-transition: all 2s;
          transition: all 2s;
    
  background-color: red;
  height: 100px;
  width: 100px;
  margin: 10px;
}

.box.in {
    opacity: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<div id="wrapper"></div>

Ответ 2

jQuery load предназначен для удаления всего, что запрашивается на странице.

Вы можете использовать возможности объектов jQuery Deferred, используя вместо этого $.get.

Взгляните на plunk.

Фрагмент кода из plunk

function load_tbody(row_id) {
  $.when($.get("load_tbody.html", function(response) {
    $('tbody').hide().html(response).fadeIn(100, function() {
      $(this).find(row_id).addClass('green');
    });
  }));
}

Я использую $.when, который будет запускать свой обратный вызов, как только будет разрешен $.get, что означает получение ответа HTML. После того, как ответ получен, он добавляется к tbody, который является методом fadedIn (fadeIn), и после его отображения класс .green добавляется в нужную строку.

Обратите внимание, что если вы идете и просто добавляете html, а затем класс в row_id, вы не сможете увидеть переход, потому что он выполняется немедленно. Небольшой приятный визуальный трюк с помощью fadeIn может выполнять работу.

Обновление

Вновь добавленные элементы в DOM, CSS3 transition не будет запущен. Это происходит главным образом из-за внутреннего браузера, который контролирует все анимации. Существует множество статей с обходными решениями по этой проблеме, а также ответы stackoverflow. Там можно найти дополнительные ресурсы, которые, я считаю, могут объяснить эту тему намного лучше меня.

Этот ответ о том, чтобы сделать шаг назад и изменить часть функциональности, которая отображает динамические элементы в DOM, без использования setTimeout или requestAnimationFrame. Это просто еще один способ достичь того, чего вы хотите достичь, ясным и последовательным образом, поскольку jQuery работает в браузерах. fadeIn(100, ... - это то, что необходимо, чтобы догнать следующий доступный фрейм, который браузер собирается отобразить. Это может быть намного меньше, ценность просто для удовлетворения визуальной эстетики.

Другим обходным решением является не использование переходов вообще, а использование animation. Но из моих тестов это не удается в IE Edge, хорошо работает в Chrome, Firefox.

Посмотрите на следующие ресурсы:

Обновление 2

Взгляните на спецификацию, так как интересный материал лежит там в CSS3 transitions.

... Эта обработка набора одновременных изменений стиля называется событием изменения стиля. (Реализации обычно имеют событие изменения стиля, соответствующее их желаемой частоте обновления экрана, и когда для API script необходим новейший вычисленный стиль или информация о макете, который зависит от него.)

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

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

Ответ 3

Это обычная проблема, вызванная браузерами. В принципе, когда вставлен новый элемент, он не вставлен сразу. Поэтому, когда вы добавляете класс, он все еще не находится в DOM и как он будет отображаться, вычисляется после добавления класса. Когда элемент добавляется в DOM, он уже имеет зеленый фон, у него никогда не было белого фона, поэтому переход не требуется. Есть способы обхода этого, как предложено здесь и там. Я предлагаю использовать requestAnimationFrame следующим образом:

function load_tbody(row_id) {
    $('tbody').load("load_tbody.php", function() {
        requestAnimationFrame(function() {
            $(row_id).addClass('green');
        });
    });
}

Edit:

Похоже, что вышеупомянутое решение не работает для всех случаев. Я нашел интересный hack здесь, который запускает событие, когда элемент действительно разбирается и добавляется в DOM. Если вы измените цвет фона после добавления элемента, проблема не возникнет. Fiddle

Изменить: Если вы хотите попробовать это решение (не будет работать ниже IE9):

Включите этот CSS:

@keyframes nodeInserted {  
    from {  
        outline-color: #fff; 
    }
    to {  
        outline-color: #000;
    } 
}

@-moz-keyframes nodeInserted {  
    from {  
        outline-color: #fff; 
    }
    to {  
        outline-color: #000;
    }  
}

@-webkit-keyframes nodeInserted {  
    from {  
        outline-color: #fff; 
    }
    to {  
        outline-color: #000;
    }  
}

@-ms-keyframes nodeInserted {  
    from {  
        outline-color: #fff; 
    }
    to {  
        outline-color: #000;
    } 
}

@-o-keyframes nodeInserted {  
    from {  
        outline-color: #fff; 
    }
    to {  
        outline-color: #000;
    }  
} 

.nodeInsertedTarget {
    animation-duration: 0.01s;
    -o-animation-duration: 0.01s;
    -ms-animation-duration: 0.01s;
    -moz-animation-duration: 0.01s;
    -webkit-animation-duration: 0.01s;
    animation-name: nodeInserted;
    -o-animation-name: nodeInserted;
    -ms-animation-name: nodeInserted;        
    -moz-animation-name: nodeInserted;
    -webkit-animation-name: nodeInserted;
}

И используйте этот javascript:

nodeInsertedEvent= function(event){
        event = event||window.event;
        if (event.animationName == 'nodeInserted'){
            var target = $(event.target);
            target.addClass("green");
        }
    }

document.addEventListener('animationstart', nodeInsertedEvent, false);
document.addEventListener('MSAnimationStart', nodeInsertedEvent, false);
document.addEventListener('webkitAnimationStart', nodeInsertedEvent, false);

function load_tbody(row_id) {
    $('tbody').load("load_tbody.php", function() {
        $(row_id).addClass('nodeInsertedTarget');
    });
}

Может быть превращено в общее решение или библиотеку. Это просто быстрое решение.

Ответ 4

Основным решением является понимание setTimeout(fn, 0) и его использование.

Это не относится к анимации CSS или jQuery load. Это ситуация, когда вы выполняете несколько задач в DOM. И время задержки вообще не важно, основная концепция - использование setTimeout. Полезные ответы и руководства:

function insertNoDelay() {
	$('<tr><td>No Delay</td></tr>')
		.appendTo('tbody')
		.addClass('green');
}

function insertWithDelay() {
	var $elem = $('<tr><td>With Delay</td></tr>')
		.appendTo('tbody');
	
	setTimeout(function () {
		$elem.addClass('green');
	}, 0);
}
tr { transition: background-color 1000ms linear; }
.green { background: green; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<table>
	<tbody>
		<tr><td>Hello World</td></tr>
	</tbody>
</table>


<button onclick="insertNoDelay()">No Delay</button>
<button onclick="insertWithDelay()">With Delay</button>