Javascript-анімація елементів як в jQuery, тільки своїми руками

Часто при пошуку відповіді на питання, як зробити ту чи іншу до цього незнайому річ, програміст знайомиться з досвідом колег. І досить часто у нас, у фронтенд-розробці, можна побачити поради наступного змісту: мовляв, просто підключи ту чи іншу бібліотеку, просто постав той чи інший плагін, просто перепиши проект на Ангуляре (просто_на_Ангуляре ))) )і не треба забивати собі голову сторонніми речами.

Однак, іноді стоять справді творчі завдання, і звичайний копіпаст не рятує демократію (чесно кажучи, він майже ніколи не рятує). Про один з таких випадків я і хочу розповісти поважній публіці.

Передісторія така. Ми в компанії розробляли продукт, клієнтська частина якого повинна була бути написана на чистому Javascipt. В якийсь момент для реалізації всіх можливостей продукту ми зрозуміли, що не можемо обійтися без анімації елементів (розкриття елементів, розчинення, плавне переміщення по екрану і т. п.)

Самим логічним кроком уявлялося просто піти і подивитися, як анімація влаштована в тому ж jQuery, і по можливості повторити рішення в своєму коді. Однак, навіть при побіжному погляді стало зрозуміло, що код jQuery не так простий для розуміння всіх залежностей. І потім, керівництво проекту надав достатньо часу, щоб, не озираючись на досвід колег, сісти і зробити рішення самому. Зараз, озираючись назад, розумію, що це був прекрасний досвід вирішення дійсно складної задачі в клієнтському програмуванні.

Отже, розглянемо задачу на верхньому рівні

У нас є наступні умови:

1) Анимируется будь-який елемент на сторінці, кількість елементів не обмежена
2) Список анимируемых властивостей заданий (лінійні розміри, положення, відступи, прозорість)
3) Анімація повинна управлятися через параметри (час виконання, функція швидкості виконання)
4) По завершенню анімації викликається будь-який довільний коллбек
5) Анімація в будь-який момент може бути перервана

Швидше за все, через пункти 1-4 буде відповідати одна глобальна функція, що викликається зі списком параметрів, за пункт 5 буде відповідати окрема спеціальна функція. Всього дві (насправді виявилося, що три :) ). Далі я розповім хід написання функцій з короткими поясненнями, навіщо існує той чи інший блок і як він працює, і врешті-дам посилання на повний код, а також приклад.

Почнемо

Відразу створюємо var $ = {d: window.document, w: window}, щоб не лізти прямо в window.

Створюємо основну функцію анімації:

$.w.ltAnimate = function (el, props, opts, cb) {});

Тут el — анимируемый елемент, props — сутність анімації, opts — характеристики, cb — коллбек.

Починаємо її наповнювати. Для збереження контексту використовуємо self, для однозначного визначення анімації id. Відразу виконуємо необхідні перевірки:

Код 1

var self = this,
id = new Date().getTime(); // id анімації
self._debug = true;
// перевіряємо елемент
if ((typeof el == "string") && el) el = this.ltElem(el);
if ((typeof el != "object") || !el || (typeof el.nodeType != "number") || (el.nodeType > 1)) {
doFail("Немає анимируемого елемента");
return;
} 
// перевіряємо аргумент opts
switch (typeof opts) {
case "number":
opts = {duration: opts};
break;
case "function":
opts = {cbDone: opts};
break;
case "object":
if (!opts) opts = {};
break;
default:
opts = {};
}
if (typeof cb == "function") opts.cbDone = cb;
// встановлюємо умовчання
var defaultOptions = {
tick : 30, // період відтворення нового кадру в мілісекундах, задається тільки тут
duration : 1000, // тривалість виконання анімації
easing : 'linear', // функція розрахунку параметрів
cbDone : function() { // коллбек після вдалого виконання
if (self._debug) $.w.console.log("Анімація [id: " + id + "] пройшла вдало");
},
cbFail : function() { // коллбек після невдалого виконання
if (self._debug) $.w.console.log("Анімація [id: " + id + "] пройшла невдало");
}
}


Зверніть увагу:: обробка помилок зручна через спеціальну функцію doFail.

Тепер дії, які необхідно вчинити з елементом, вносимо в масив:

Код 2

// отримуємо масив, що будемо виконувати
var instructions = [];
for (var key in props) {
if (!props.hasOwnProperty(key)) continue;
instructions.push([key, props[key]]);
}
// якщо виконувати нічого, видаємо помилку
if (instructions.length === 0) {
doFail("Не сказано, що робити з елементом");
return;
}


Стандартні опції перезаписуємо клієнтськими там, де це зазначено (природно, з перевірками):

Код 3

// перезаписуємо опції клієнтськими значеннями
var optionsList = [], 
easing = {linear: 1, swing:1, quad:1, cubic:1};
for (var key in opts) {
if (!opts.hasOwnProperty(key)) continue;
switch (key) {
case "duration":
if (typeof opts[key] != "number") {
$.w.console.log("ltAnimate(): Увага! Тривалість анімації задається числом. Буде застосована стандартна тривалість");
continue;
}
break;
case "easing":
if (typeof easing[opts[key]] == "undefined") {
$.w.console.log("ltAnimate(): Увага! Невідоме значення easing. Буде застосована стандартна функція");
continue;
}
break;
case "cbDone":
case "cbFail":
if (typeof opts[key] != "function") {
$.w.console.log("ltAnimate(): Увага! Коллбек повинен бути функцією!"); 
continue;
}
break;
default: 
$.w.console.log("ltAnimate(): Увага! Невідомий параметр у списку опцій!");
continue;
}
optionsList.push([key, opts[key]])
}
// формуємо options на основі defaultOptions
var options = defaultOptions;
if (optionsList.length) {
for (var i=0; i < optionsList.length; i++) {
if (optionsList[i][0] == 'тривалість') options.duration = optionsList[i][1];
if (optionsList[i][0] == 'easing') options.easing = optionsList[i][1];
if (optionsList[i][0] == 'cbDone') options.cbDone = optionsList[i][1];
if (optionsList[i][0] == 'cbFail') options.cbFail = optionsList[i][1];
}
}


Тепер трохи повернемося в реальний світ.

Справа в тому, що анімація викликається в будь-який момент на будь-якому елементі. Відповідно, спочатку нам потрібно прийняти параметри цього елемента в цій точці часу, щоб зрозуміти, як його видозмінити.


// об'єкт, куди будуть записуватися параметри елемента при старті анімації
var startParams = {};

Тут, нарешті, до нас доходить, що потрібна буде ще якась додаткова функція, за допомогою якої ми опишемо анимируемый елемент (про неї трохи далі). А поки зробимо ще одну важливу перевірку.

Кількість і черга анімацій

Так, завдання стає все складніше. Справа в тому, що анімація може викликатися на елементі, який вже знаходиться в стані виконання попередньої анімації (і ще не закінчив її). Наша функція повинна бути універсальна, тому вона повинна вміти приймати і коректно обробляти ланцюг незалежних викликів.

Нижче дан код. До нього є построкові пояснення, тому просто наводжу повністю цей блок:

Код 4

// якщо друга або більше анімація на цьому об'єкті
if (el.ltAnimateQueue && el.ltAnimateQueue.length > 0) {

// дивимося, через скільки її потрібно буде спробувати виконати (точно передбачити не можна, т. к. кілька мс йде на виконання коду)
var animateEnds = 1,
timeNow = new Date().getTime();

for (var i=0; i < el.ltAnimateQueue.length; i++) {
if (i == 0) {
animateEnds = el.ltAnimateQueue[i][1] - timeNow + el.ltAnimateQueue[i][0];
} else {
animateEnds += el.ltAnimateQueue[i][1];
}
}

// заносимо анімацію в чергу анімацій
el.ltAnimateQueue.push([timeNow + animateEnds, options.duration]);

// через порахована час дивимося, чи справді всі попередні анімації завершилися і можна виконувати цю
var thisTimeout = $.w.setTimeout(function(){
checkAnimation();
}, animateEnds);

// масив таймаутів, які поставлені для активації анімації, потрібен при виклику ltAnimateStop
if (!el.ltAnimateTimeouts) {
el.ltAnimateTimeouts = [];
}
el.ltAnimateTimeouts.push(thisTimeout);

// перша анімація на об'єкті
} else {
// створюємо чергу виконання анімацій, якщо перша анімація на елементі
el.ltAnimateQueue = [[new Date().getTime(), options.duration]];
startAnimation();
}

// перевіряємо, чи дійсно ніякі анімації не виконуються і можна запускати цю
function checkAnimation() {
// якщо ніяких анімацій не виконується, то відразу запускаємо
if (!el.ltAnimateIsDoing) {
startAnimation();
} else {
// періодично опитуємо, дійсно анімації закінчилися
function _check() {
if (!el.ltAnimateIsDoing) {
$.w.clearInterval(_checking);
startAnimation();
}
}
var _checking = $.w.setInterval(_check, 30);
}
}


Ми готові стартувати виконання анімації, її запуск проводиться функцією startAnimation(). Ось тут-то, на самому початку, ми і описуємо наш елемент через його властивості (робити це раніше не можна, т. к. елемент, доки анімація перебувала в черзі, міг бути змінений):

Код 5

function startAnimation() {
// прапор виконання анімації
el.ltAnimateIsDoing = true;

// розміри елемента
var startStyles = self.ltStyle(el);

// запам'ятовуємо стартові значення властивостей елемента
startParams.left = parseInt(startStyles.left);
startParams.right = parseInt(startStyles.right);
startParams.top = parseInt(startStyles.top) + 0.01;
startParams.bottom = parseInt(startStyles.bottom) - 0.01;
startParams.width = parseInt(startStyles.width);
startParams.height = parseInt(startStyles.height);
startParams.opacity = parseFloat(startStyles.opacity);
startParams.marginTop = parseInt(startStyles.marginTop);
startParams.marginBottom = parseInt(startStyles.marginBottom);
startParams.marginLeft = parseInt(startStyles.marginLeft);
startParams.marginRight = parseInt(startStyles.marginRight);
startParams.parentWidth = parseInt(self.ltStyle(el.parentNode).width);
startParams.parentHeight = parseInt(self.ltStyle(el.parentNode).height);

// перевірки та підстановки для Chrome, IE
for (key in startParams) {
if (key == 'left' && !startParams[key]) {
startParams.left = startParams.parentWidth - startParams.right - startParams.width || 0;
}
if (key == 'right' && !startParams[key]) {
startParams.right = startParams.parentWidth - startParams.left - startParams.width || 0;
}
if (key == 'bottom' && !startParams[key]) {
startParams.bottom = startParams.parentHeight - startParams.top - startParams.height || 0;
}
if (key == 'top' && !startParams[key]) {
startParams.top = startParams.parentHeight - startParams.bottom - startParams.height || 0;
}
}
// виконання анімації
el.currentAnimation = new doAnimation({
element : el,
delay : defaultOptions.delay
});
}


Як видно, тут ми звертаємося до спеціальної функції, яка дасть нам опис стилів елемента. Пам'ятаючи про те, що наш код повинен бути на чистому JS, а проект повинен адекватно працювати на браузері IE8 (так-так, ми пишемо справжній комерційний код, який продається за гроші в різні країни, тому аргумент «це не модно» не приймається!), стискаємо всі волю в кулак і йдемо забарывать ослячі заморочки:

Код 6

/**
* Отримання всіх стилів елемента (якщо подано лише el) або значення конкретного стилю (в styleName передається рядок).
* opts - об'єкт, властивість computed за замовчуванням дорівнює true. Якщо так, повертає кінцевий стиль елемента, якщо false - инлайновый.
* Якщо елемент поданий неявно (наприклад, тег div) і при пошуку з'ясовується, що подібних елементів на сторінці кілька, повертається порожній рядок.
* Для IE8 виконується перетворення %, auto, thin/medium/thick в нормальний вигляд.
* Opacity для IE8 повертається в нормальному вигляді (від 0 до 1)
*
* @param {DOM} el - оброблюваний елемент
* @param {string} style - назва стилю, значення якого потрібно отримати
* @param {Object} opts - додаткові опції операції
*
* @returns {(number|string)} value - обчислене значення
*/
$.w.ltStyle = function(el, styleName, opts) { 
if (!opts || typeof opts != 'object' || typeof opts.computed != 'boolean') opts = {computed : true};
if (typeof el == 'string') el = this.ltElem(el);
// якщо повертається масив (NodeList), то повертаємо порожній рядок
if (!el || !el.nodeType || (el.nodeType != 1)) return;
var _style;
// в IE8 замість getComputedStyle є currentStyle
if (!$.w.getComputedStyle) {
var __style = el.currentStyle,
_style = {};
for (var i in __style) {
_style[i] = __style[i];
}
// стилі, для яких в IE8 існують рідні стилі: pixelLeft, pixelRight і так далі - їх можна взяти безпосередньо, не рахуючи
var pixel = {
left: 1,
right: 1,
width: 1,
height: 1,
top: 1,
bottom: 1
};
// для цих стилів використовуємо хак http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
var other = {
paddingLeft: 1,
paddingRight: 1,
paddingTop: 1,
paddingBottom: 1,
marginLeft: 1,
marginRight: 1,
marginTop: 1,
marginBottom: 1
};
var leftCopy = el.style.left;
var runtimeLeftCopy = el.runtimeStyle.left;
// для всіх стилів відразу
if (!styleName) {
// товщина кордонів в IE8 приходить у вигляді прикметників, наводимо в нормальний вигляд
for (c in _style) {
if (!_style.hasOwnProperty©) continue;
if (c.indexOf("border") !== 0) continue;
switch (_style[c]) {
case "thin":
_style[c] = 2;
break;
case "medium":
_style[c] = 4;
break;
case "thick":
_style[c] = 6;
break;
default:
_style[c] = 0;
}
}
//pixel
for (var key in pixel) {
_style[key] = el.style["pixel" + key.charAt(0).toUpperCase() + key.replace(key.charAt(0), "")];
}
// варіант заміни getComputedStyle для деяких параметрів
for (var key in other) {
el.runtimeStyle.left = el.currentStyle.left;
el.style.left = _style[key];
_style[key] = el.style.pixelLeft;
el.style.left = leftCopy;
el.runtimeStyle.left = runtimeLeftCopy;
}
// для одного вибраного стилю
} else { 
if (_style[styleName]) {
if (style.indexOf("border") === 0)
switch (_style[styleName]) {
case "thin":
_style[styleName] = 2;
break;
case "medium":
_style[styleName] = 4;
break;
case "thick":
_style[styleName] = 6;
break;
default:
_style[styleName] = 0;
}
} else {
if (pixel[styleName]) {
_style[styleName] = el.style["pixel" + key.charAt(0).toUpperCase() + key.replace(key.charAt(0), "")];
} else {
el.runtimeStyle.left = el.currentStyle.left;
el.style.left = _style[styleName];
_style[styleName] = el.style.pixelLeft;
el.style.left = leftCopy;
el.runtimeStyle.left = runtimeLeftCopy;
}
}
}
// милиця для opacity IE8
if (_style.filter.match('alpha')) {
_style.opacity = _style.filter.substr(14);
_style.opacity = parseInt(_style.opacity.substring(0, _style.opacity.length - 1)) / 100;
} else {
_style.opacity = 1;
}
// нормальні браузери
} else { 
if (opts.computed) {
_style = $.w.getComputedStyle(el, null);
} else { 
_style = el.style.styleName; 
}
}
if (!styleName) {
return _style || ";
} else {
return _style[styleName] || ";
}
};


Ось, здавалося б, всього-то ділов: отримати стилі елемента! Ан-ні, і тут є поле для творчості.

Нарешті, елемент повністю описаний, всі параметри анімації перевірені та приведені в нормативний вид. Тепер пишемо код виконання. Він цілком укладений у функції doAnimation(params) {};

У першій її частині, яка наведена нижче, найцікавіше — розшифровка інструкцій анімації («що робити з об'єктом?»). Сподіваюся, всі пам'ятають, що деякі властивості елемента (розміри, положення, відступи) можуть задаватися не тільки пікселями, але і відсотками:

Код 7

// значення параметра
var val = instructions[i][1].toString();
// дивимося, заданий параметр у відсотках
val.match(/\%/) ? percent = true : percent = false;
val = parseFloat(val);
var x;
switch (instructions[i][0]) {
case 'top' :
x = function(factor, val, percent) {
element.style.bottom = ";
element.style.top = startParams.top - (startParams.top - (percent ? startParams.parentHeight * val / 100 : val))*factor + 'px';
};
break;
case 'bottom' :
x = function(factor, val, percent) {
element.style.top = ";
element.style.bottom = startParams.bottom - (startParams.bottom - (percent ? (startParams.parentHeight * val / 100) : val))*factor + 'px';
};
break;
case 'left' :
x = function(factor, val, percent) {
element.style.right = ";
element.style.left = startParams.left - (startParams.left - (percent ? (startParams.parentWidth * val / 100) : val))*factor + 'px';
};
break;
case 'right' :
x = function(factor, val, percent) {
element.style.left = ";
element.style.right = startParams.right - (startParams.right - (percent ? (startParams.parentWidth * val / 100) : val))*factor + 'px';
};
break;
case 'width' :
x = function(factor, val, percent) {
element.style.width = startParams.width - (startParams.width - (percent ? (startParams.width * val / 100) : val))*factor + 'px';
};
break;
case 'height' :
x = function(factor, val, percent) {
element.style.height = startParams.height - (startParams.height - (percent ? (startParams.height * val / 100) : val))*factor + 'px';
};
break;
case 'непрозорість' :
x = function(factor, val, percent) {
// IE8
if (!$.w.getComputedStyle) {
element.style.filter = 'alpha(opacity=' + (startParams.opacity - (startParams.opacity - (percent ? (val / 100) : val))*factor) * 100 + ')';
} else {
element.style.opacity = startParams.opacity - (startParams.opacity - (percent ? (val / 100) : val))*factor;
}
}
break;
case 'marginTop' :
x = function(factor, val, percent) {
element.style.marginBottom = 'auto';
element.style.marginTop = startParams.marginTop - (startParams.marginTop - (percent ? (startParams.height * val / 100) : val))*factor + 'px';
};
break;
case 'marginBottom' :
x = function(factor, val, percent) {
element.style.marginTop = 'auto';
element.style.marginBottom = startParams.marginBottom - (startParams.marginBottom - (percent ? (startParams.height * val / 100) : val))*factor + 'px';
};
break;
case 'marginLeft' :
x = function(factor, val, percent) {
element.style.marginRight = 'auto';
element.style.marginLeft = startParams.marginLeft - (startParams.marginLeft - (percent ? (startParams.width * val / 100) : val))*factor + 'px';
};
break;
case 'marginRight' :
x = function(factor, val, percent) {
element.style.marginLeft = 'auto';
element.style.marginRight = startParams.marginRight - (startParams.marginRight - (percent ? (startParams.width * val / 100) : val))*factor + 'px';
}
break;
// якщо спроба анімувати непідтримувану властивість, просто нічого не робимо
default : x = function(){};
}
// заносимо виконувані функції в масив
exec.push([x, val, percent]);
}

var eLength = exec.length;


Нарешті, ми підібралися до самого серця механізму

Давайте подумаємо, що взагалі таке анімація? В реальному світі будь-який рух суть задум, який розгортається в часі.

Самурай, взмахнувший мечем
Подібний самураєві, не взмахнувшему мечем,
Але тільки взмахнувший мечем
У світі програмування ми повинні перевести будь-яку поезію в цифру. Тобто, ближче до справи, описати елемент в будь-який момент часу через зрозумілі величини.

Тепер замислимося — чи потрібен нам «будь-який момент»? Нам доступно управління поведінкою елемента в проміжки часу від мілісекунди. На ділі, звичайно, необхідний інтервал в описі елемента є компроміс між продуктивністю браузера і здатність людського мозку складати окремі дискретні картинки в одну сцену. Дослідним шляхом встановив, що 30 мілісекунд буде в самий раз.

Іншими словами, анімація — це послідовне, через рівні проміжки часу, зміна стану елемента. А за рівні проміжки часу у нас відповідає setInterval:


el.ltAnimateInterval = $.w.setInterval(function(){
_animating();
}, options.tick);

Ось це і є наш «движок» анімації.

Зверніть увагу:: ми встановлюємо інтервал як властивість елемента, адже нам в подальшому потрібен доступ ззовні, щоб припинити виконувати анімацію (тобто обнулити інтервал).

Нарешті, функція відтворення елемента, яка виконується через задані проміжки часу весь час виконання анімації:

Код 8

// jumpToEnd - true/false - говорить про те, чи слід припинити анімацію в кінцевій точці, відразу перейшовши в кінцеву точку
// вивід
function _animating(param, jumpToEnd, callback) {
counter++;
// змінна приймає значення від 0 до 1
var progress = counter / animationLength;
// вимикаємо анімацію за допомогою stopAnimation
if (param == animationLength) {
$.w.clearInterval(el.ltAnimateInterval);
// якщо потрібно завершити в кінцевій точці
if (jumpToEnd) _step(getProgress(1)); 
// видаляємо анімацію з черги анімацій
el.ltAnimateQueue.splice(0, 1);
// вимикаємо прапор виконання анімації
el.ltAnimateIsDoing = false;
// зупинка, якщо явно не вказано з приводу коллбэка
if (!callback) {
try {
options.cbDone();
} catch(e) {
doFail(e);
}
} else {
try {
callback();
} catch(e) {
doFail(e);
}
}
return false;
}
// вимикаємо анімацію, якщо пройдені всі кроки
if (progress > 1) {
// робимо заключний крок, без нього анімація трохи не доїжджає до фінальної точки (progress змінюється дискретно, останнє значення 0.99...)
_step(getProgress(1));
$.w.clearInterval(el.ltAnimateInterval);
// видаляємо анімацію з черги анімацій
el.ltAnimateQueue.splice(0, 1);
// вимикаємо прапор виконання анімації
el.ltAnimateIsDoing = false;
try {
options.cbDone();
} catch(e) {
doFail(e);
}
return false;
}
_step(getProgress(progress));
}


Як бачите, перші два блоки коду функції — це механізм вимикання анімації (думаю, особливо розбирати не потрібно), а сама побудова робиться функцією _step(getProgress(progress)):


function _step(factor) {
for (var i=0; i < eLength; i++) {
var s = exec[i][0],
val = exec[i][1],
percent = exec[i][2];
s(factor, val, percent);
}
}

Тут розберемо все максимально детально:

  • eLength ми вже визначили раніше — довжина списку директив анімації («що робити з елементом?»)
  • s — функція, яка змінює параметр елемента (див. switch (instructions...) в doAnimation)
  • val — кінцеве значення параметра, до якого прийде анімація
  • percent — параметр визначається у відсотках або немає
Тепер про factor, з яких викликається ця функція. Це обчислюваний параметр, що говорить нам, наскільки слід змінити параметр елемента (по дорозі від початкового значення до кінцевого) в даний конкретний момент часу, який розуміється як точка на лінії часу відрізку від 0 до 1. Це вже було вище:


// змінна приймає значення від 0 до 1
var progress = counter / animationLength;

Йти по відрізку можна з рівною швидкістю, або слідуючи поведінки однієї з стандартних функцій анімації:


// змінна для рахунку, згідно заданим при виклику параметрами
function getProgress(p) {
switch (options.easing) {
case 'linear' : return p; break;
case 'swing' : return 0.5 - Math.cos(p * Math.PI ) / 2; break
case 'quad' : return Math.pow(p, 2); break;
case 'cubic' : return Math.pow(p, 3); break;
default : return p;
}
}

По-російськи це звучить складно, та (або я просто не вмію застосовуй російської мови досить). Але на картинці все наочніше, вісь абсцис — час, вісь ординат — значення змінюваного параметра:

image

Таким чином, суть механізму наступна: через рівні проміжки часу ми запитуємо getProgress, на якій стадії анімації (по дорозі від початкової точки до кінцевої) ми знаходимося, а потім йдемо з цим знанням у _step і виконуємо функції зміни параметрів зі списку.

І останнє, що ми прописуємо в doAnimation, це інтерфейс виклику зупинки анімації:

Код 9

// інтерфейс зупинки анімації
el.stopAnimation = function(jumpToEnd, callback) {
_animating(animationLength, jumpToEnd, callback);
// очищаємо таймаут черги очікування анімації
if (el.ltAnimateTimeouts) {
for (var i=0; i < el.ltAnimateTimeouts.length; i++) {
$.w.clearTimeout(el.ltAnimateTimeouts[i])
}
el.ltAnimateTimeouts = [];
}
}


Виклик зупинки анімації простий: ми просто вказуємо елемент, говоримо, потрібно в момент зупинки перейти в кінцеву точку анімації, і вказуємо, якщо потрібно, новий коллбек.

Для припинення анімації у нас є окрема глобальна функція:

Код 10

/*
* Остановкка анімації: елемент, перехід в кінцеву точку (true/false) та зупинити виконання коллбэка (true/false), два останніх необов'язково
*/
$.w.ltAnimateStop = function(el, jumpToEnd, callback) {
// зупиняємо анімацію елемента, якщо вона вже є
if (!el.ltAnimateInterval) return false;
el.stopAnimation(jumpToEnd, callback);
};


Ну і саме останнє — обробник помилок. Він пишеться в $.w.ltAnimate і викликається, якщо потрібно, з неї ж:

Код 11

// обробка помилок
function doFail(text) {
if (self._debug._enabled) {
if ((typeof text != "string") || !text) text = "З анімацією [id: " + id + "] щось не так."; 
$.w.console.log("ltAnimate(): Увага! " + text);
}
if (opts.cbFail) {
try {
opts.cbFail();
} catch (e) { 
$.w.console.log("ltAnimate(): Увага! Помилка виконання коллбэка анімації [id: " + id + ", " + e.name + ": " + e.message + "]");
}
}
}


Спробувати поганяти прямоугольнички самому можна тут

Там же можна взяти повний вихідний код всіх трьох функцій.

_____________________________________

ps. Оскільки код писався півтора роки тому, подробиці деяких моментів, чому це було зроблено саме так, вже почали стиратися з пам'яті. Однак, якщо ви задасте питання, я постараюся максимально точно згадати, що мав на увазі, коли писав код.

Безумовно, представлений код не є ідеальним. Буду радий конструктивним зауваженням і зазначенням на помилки і доповнення.

Також було б цікаво обговорити, як можна підійти до завдання розробки елементів анімації в браузері, використовуючи інший підхід.
Джерело: Хабрахабр

0 коментарів

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