Заняття на вечір: ефект Explode на pure JS

Сьогодні ми розглянемо, як реалізується ефект Explode без використання яких-небудь додаткових бібліотек.

→ Демо-сторінка: 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 = (ширина * 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:

image
Справа залишилася за малим: анімувати кожну комірку у заданому напрямку. У тілі циклу викликаємо допоміжну функцію 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-исходник перебуває тут.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.