> Демо-страничка: Explode.js
Что требуется сделать:
- Разрезать элемент на N*N ячеек
- Поместить в каждую ячейку копию «взрываемого» элемента
- Сместить margin-ами содержимое каждой ячейки
- Анимировать каждую ячейку в нужном направлении
Разрезаем элемент
Это самая простая часть. Дано: некий элемент (например, картинка или блок с текстом) для взрывания и количество ячеек N*N. Пишем цикл, в котором будем создавать N*N дивов и помещать, а затем сдвигать клон нашего элемента.
Объявляем нужные для работы переменные:
function Explode( elem, N ) {
N = N || 3;
var pos = elem.getBoundingClientRect();
var top = pos.top;
var left = pos.left;
var width = Math.round( elem.clientWidth / N );
var height = Math.round( elem.clientHeight / N );
// [...]
Создаём ячейки:
for ( var i = 0; i < N; i++ ) {
for ( var j = 0; j < N; j++ ) {
var tile = document.createElement( "DIV" );
document.body.appendChild( tile );
// [...]
}
}
Задаём ячейке необходимые стили (этот код уже внутри цикла):
tile.style.position = "fixed";
tile.style.width = width + "px";
tile.style.height = height + "px";
tile.style.top = (top + height * i) + "px";
tile.style.left = (left + width * j) + "px";
// поскольку мы хотим показывать в ячейке лишь часть элемента,
// то скрываем всё что не влезает
tile.style.overflow = "hidden";
Теперь вставляем в каждую ячейку клон элемента:
var tileContent = elem.cloneNode( true );
tile.appendChild( tileContent );
Сейчас надо сдвинуть клон в каждой ячейке, чтобы совокупность всех ячеек выглядела точно так же, как цельная картинка (сейчас же в каждой ячейке просто её верхний левый угол).
tileContent.style.marginTop = (-height * i - pos.top) + "px";
tileContent.style.marginLeft = (-width * j - pos.left) + "px";
Т.е. сдвигаем вверх на высоту ячейки, помноженную на номер ячейки по вертикали. Аналогично сдвигаем влево. -pos.top и -pos.left нужны на случай, если элемент сдвинут относительно края окна (а он, скорее всего, сдвинут). Если не отнять эти величины, то в ячейке будет пустота либо не тот кусок элемента — так как учитываются и «родные» отступы.
Первая половина задачи выполнена — картинка «разрезана» на ячейки, с каждой из которых можно работать по отдельности (а именно анимировать в разные стороны), но при этом для пользователя картинка выглядит без изменений.
Теперь надо определить, в каком направлении анимировать каждую из ячеек. Т.е. верхняя левая должна улетать вверх и влево, ячейка справа от неё — просто вверх и т.д., тем самым создавая эффект «взрыва». Надо бы написать вспомогательную функцию direction(i, j, N), где i и j — номер ячейки (в цикле), а N — кол-во ячеек по вертикали и горизонтали.
Попытка №1
Сначала была идея возвращать некую цифру (например, 0 — вверх, 1 — вверх и вправо и т.д. по часовой стрелке) и по ней определять направление. Это на первый взгляд показалось удобным и интуитивным, но на деле породило кучу повторяющегося кода в стиле:
function direction (i, j, N) {
if ( i == 0 && j == 0 ) return 7; // влево и вправо
if ( i == 0 && j < N - 1 ) return 0; // только вверх
if ( i == 0 && j == N - 1 ) return 1; // вверх и влево
// [...]
Я даже боюсь представить, как это анимировать. Писать if для каждого случая? Нет, нужно что-то другое.
Этот способ имеет два недостатка:
— громоздкость
— не совсем корректные значения. Например, при N=5 верхняя левая ячейка анимируется влево и вверх, тут всё правильно, однако следующая справа от неё ячейка будет улетать строго вверх, хотя хотелось бы вверх и немного влево (но не так сильно)
Попытка №2
Создать двумерный массив N*N, заполнить его нулями, назначить элементу (i, j) значение 1, и «искать» его, рекурсивно срезая с массива по одному слою (т.е. делая из массива 5х5 массив 4х4 и так далее). Очевидный плюс — нет кучи if-ов и громоздкого кода. Минус всё тот же — не совсем нужные значения.
К сожалению, старые исходники я не сохранил, а писать эту энигму снова нет никакого желания — поэтому на этот раз без примеров исходников.
Попытка №3, успешная
Векторы. Это компактное и удобное решение, которое также решает проблему с «не совсем корректными значениями». Мы будем узнавать, как далеко ячейка находится от центра, и вычислять некий вектор (x, y), и основываясь на x и y, анимировать ячейку по направлениям.
function direction( i, j, N) {
return [ (N-1) / 2 - i, j - (N-1) / 2 ]
};
Как же всё было просто…
Для большей наглядности — какие значения задаются ячейкам при N=7:
Дело осталось за малым: анимировать каждую ячейку в заданном направлении. В теле цикла вызываем вспомогательную функцию animateStart для каждой ячейки:
animateStart( tile, left + width * j, top + height * i, direction( i, j, N ) );
где первым аргументом передаётся сама ячейка, вторым и третьим — её начальные координаты, а последним — вектор (x, y), который представлен массивом из двух чисел.
Теперь приступим к написанию функции animateStart.
function animateStart( elem, posX, posY, dir ) {
var vY = dir[0]; // вектор по вертикали
var vX = dir[1]; // вектор по горизонтали
var start = new Date().getTime(); // время начала анимации
// toX и toY - конечные координаты
// например, у верхней левой ячейки вектор (3, -3), таким образом
// в этом примере она сдвинется на 150 пикселей вверх и
// на столько же влево
var toY = posY - 50 * vY;
var toX = posX + 50 * vX;
setTimeout( animate, 10 ); // запускаем анимацию
// [...]
Теперь напишем функцию animate. Это — прогресс анимации. Она будет запускаться каждые 10 миллисекунд, ненамного сдвигая ячейку по направлениям. Так и создаётся эффект плавной анимации.
function animate () {
// прогресс анимации, в начале - 0, в конце - 1
var m = (new Date().getTime() - start) / 500;
if (m > 1) m = 1;
elem.style.top = (posY + (toY - posY) * m) + "px"; // сдвигаем ячейку
elem.style.left = (posX + (toX - posX) * m) + "px"; // по стандартной формуле
elem.style.opacity = 1 - m; // а также делаем эффект "затухания", добавляя прозрачность
if (m < 1) setTimeout( animate, 10 ); // если прогресс анимации меньше 1, запускаем функцию снова
}
Последний штрих: удаляем оригинальный элемент.
elem.parentNode.removeChild( elem );
Плагин готов! Демо-страничка в самом начале статьи, а js-исходник находится тут.
Комментарии (17)
MrCheater
10.02.2017 13:27+8Хочу дать несколько советов:
- Не учите людей анимации через
setTimeout
делать, есть жеrequestAnimationFrame
- Используйте для статей на Хабре тег
<oembed>
, чтобы все могли смотреть ваши демки, не отходя от кассы
Пример использования OEMBED<oembed>http://codepen.io/MrCheater/pen/QGqdbo</oembed>
avdept
10.02.2017 17:44А кто будет чистить DOM после окончания анимации?
DiphenylOxalate
13.02.2017 22:52Так ведь потом можно «собрать» элемент обратно, если надо.
В jquery explode такая возможность есть.
vitvad
10.02.2017 21:14+3<oftop>кто такая Анна и за что мы ее так?</oftop>
Andrew_Pinkerton
13.02.2017 22:51Аннасофи?я Робб (англ. AnnaSophia Robb, род. 8 декабря 1993, Денвер) — американская актриса. Wiki
askd
13.02.2017 17:17+1Pure CSS на коленке http://codepen.io/askd/full/ZLVqOP/
bano-notit
14.02.2017 22:01Ну не pure конечно, а PostCSS, но всё же работает)
askd
15.02.2017 09:59PostCSS только потому, что было лень писать 64 строки правил вручную. На выходе (в браузере) это всё же обычный CSS ;)
bano-notit
17.02.2017 22:17Ну… Когда на ангуляре 2 пишешь тоже в конце концов обычный js получается, так же как и со многими вещами.
KostaShah
13.02.2017 22:53Не уверен, что это возможно, но возникла вот такая идейка. Что если, после того, как мы всё размножили и расположили, то есть, сделали всё, кроме самой анимации, вместо того, чтобы скриптом двигать каждую ячейку, задать этим ячейкам и их контейнеру стили равномерного распределения с помощью flex-модели, указать контейнеру tranzition на положение и размер, а затем задать ему новое положение (выше и левее) и новые размеры (больше). Тогда, плавно расширяющийся контейнер сам плавно раздвинет ячейки в нужные стороны.
andreymal
13.02.2017 22:55Наверно, это возможно, но всё равно в сравнении с transform(translateX/Y) будет притормаживать
askd
15.02.2017 10:01Выше как раз такой пример в CodePen ;)
KostaShah
15.02.2017 10:42Да, верно, я заметил, и оценил :-) Отлично сделано, я примерно так и предполагал. Когда я писал, его не было (моё сообщение ждало модерации несколько дней).
askd
15.02.2017 11:08Модерация?! Ничего себе. Моё сразу опубликовалось (хотя у меня и плохая карма после Великого Обнуления)
- Не учите людей анимации через
andreymal
В 2017 году и без CSS3 transition+transform? Это ж фи, на мобильниках тормозит.