Jump to content
  • 0

Создаем мини-фреймворк своими руками


Great Rash
 Share

Question

Вступление

Что мы будем делать: мини-фреймворк для анимации перемещения абсолютно или относительно позиционированных на странице объектов по оси X или Y.

Сначала о главном: это мой первый опыт написания уроков, да и вообще статей, так что сильно не пинайте.

У многих возникнет вопрос, зачем все это надо, если есть уже готовые фреймворки (jQuery, Prototype и т.п.)? Многие скажут излюбленную фразу про велосипед. Но вы их не слушайте, они просто буржуи – любят жить на всем готовеньком. А вот мы с вами – рабочий класс! А если серьезно, то иногда нет смысла подключать большой фреймворк ради одной единственной функции, в нашем случае анимации перемещения объекта. Ну и, опять же, опыт.

Для начала немного про ООП

В JavaScript’е объект можно создать двумя способами. Первый способ создание функции. В JavaScript объектом является любая функция:

function Obj() { // объекты принято называть с большой буквы
this.x = 1;
this.y = 2;
}

Ключевое слово this является ссылкой на объект Obj. This.x и this.y – это его свойства Obj, т.е., говоря просто, это локальные переменные которые действуют только внутри объекта Obj. Так же у объекта есть методы. Метод – это функция внутри объекта. Работает она тоже только внутри него. У каждого объекта в JavaScript есть свойство prototype, при помощи которого можно добавлять новые методы объекту:

function Obj(x, y) { // объявляем объект
this.x = x; // добавляем свойство X равное параметру x, переданному в функцию
this.y = y; // добавляем свойство Y равное параметру y, переданному в функцию
}

Obj.prototype.method = function() { // добавляем метод
return this.x + this.y; // который будет возвращать сумму двух свойств
}

Мы создали наш объект и добавили ему свойства и метод, теперь осталось только создать его и начать использовать. Объект создается при помощи ключевого слова new, и может быть присвоен переменной или использоваться сразу:

// можно так
var o = new Obj(4, 5);
alert(o.method()); // доступ к свойствам и методам происходит через точку

// а можно так
alert(new Obj(7, 8).method());

Неудобство такого подхода в создании объектов заключается в том, что в данном случае объект надо обязательно инициализировать при помощи ключевого слова new. Тут на помощь приходит второй способ создания объекта, а именно объектный литерал:

var Obj = {} // а вот, собственно, и он

Внутри объектного литерала свойства и методы записываются парами свойство: значение (имя_метода: функция) и отделяются запятыми. Похоже на ассоциативный массив ключ=>значение.

var Obj = { // создаем объект
x: 1, // добавляем свойство
y: 2, // добавляем свойство

method: function() { // добавляем метод
return this.x + this.y;
}
}

При таком стиле создания объекта нет необходимости в ключевом слове new и объект можно использовать сразу:

alert(Obj.method());

Но тогда остается вопрос, как передать объекту параметры? Для этого можно создать отдельный метод, скажем init (я так и делаю), где параметры будут присвоены соответствующим свойствам объекта. Там же можно вызывать какой-либо метод:

var Obj = { // создаем объект с методом init
init: function(x, y) {
this.x = x;
this.y = y;

this.method();
},

method: function() {
return this.x + this. y;
}
}

// и используем его
Obj.init(2, 3);
alert(Obj.method());

Так же в качестве параметра функции можно передать объект, для этого нужно записать в параметр объектный литерал, который потом можно использовать внутри нашего объекта. Для этого надо немного изменить метод init:

var Obj = { // создаем объект с новым методом init
init: function(params) {
this.x = params.x;
this.y = params.y;

this.method();
},

method: function() {
return this.x + this. y;
}
}

// и используем его
Obj.init({x: 5, y: 6});
alert(Obj.method());

Преимущества такого подхода заключаются в том, что нам становится совершенно неважно в каком порядке стоят передаваемые параметры:

// не важно в каком порядке стоят параметры
Obj.init({y: 6, x: 5});
alert(Obj.method());

А еще, при таком подходе мы можем добавлять дефолтные значения для некоторых параметров:

var Obj = { // создаем объект с новым методом init
init: function(params) {
this.x = params.x ? params.x : 2; // если параметра X нет, то присваиваем дефолтное значение 2
this.y = params.y ? params.y : 2; // если параметра Y нет, то присваиваем дефолтное значение 2

this.method();
},

method: function() {
return this.x + this. y;
}
}

// вызываем метод с двумя параметрами
Obj.init({x: 23, y: 32});
alert(Obj.method());

// а можно без одного из параметров
Obj.init({x: 23});
alert(Obj.method());

// а можно и без другого
Obj.init({y: 17});
alert(Obj.method());

Ну вот теперь вы должны немного представлять себе как устроены объекты в JavaScript'е и мы можем приступать собственно к построению самого фреймворка. Следите за темой, продолжение в следующем посте.

Что почитать на тему:

http://javascript.ru/tutorial/object

http://designformasters.info/posts/objectifying-javascript/

«javascript ООП» в Google.

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

Link to comment
Share on other sites

5 answers to this question

Recommended Posts

  • 0

Базовая верстка

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
<title>Test</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<script type="text/javascript" src="animate.js"></script>

<style type="text/css">
* {
margin: 0;
padding: 0;
font: 12px Arial, sans-serif;
}

.wrap {
overflow: hidden;
width: 607px;
border: 1px solid;
}

.button {
width: 50px;
height: 50px;
text-align: center;
}

.left {
float: left;
}

.right {
float: right;
}

.container {
position: relative;
width: 500px;
height: 50px;
margin: 0 auto;
background: #effeff;
}

.item {
position: absolute;
width: 50px;
height: 50px;
background: red;
}
</style>
</head>

<body>

<div class="wrap">
<div class="left">
<input class="button" type="button" value="←" />
</div>
<div class="right">
<input class="button" type="button" value="→" />
</div>
<div class="container">
<div id="test" class="item"></div>
</div>
</div>

</body>
</html>

Тут все очень просто. На стрелки мы будем тыкать, а красный квадрат будет туда-сюда ездить. Стили я выносить в отдельный файл не стал, а скрипт будет лежать в файле animate.js.

Link to comment
Share on other sites

  • 0

Создаем базовый объект

Итак нам надо продумать что должен уметь наш аниматор:

во-первых, он должен уметь находить объект по id если ему передали id, так же было бы неплохо если бы ему можно было бы передать ноду вместо id (это иногда удобней);

во-вторых, он должен уметь двигать объект по оси X или по оси Y, в зависимости от параметра который мы передадим ему;

в-третьих, неплохо было бы передавать ему разные единицы измерения (пиксели, проценты);

в-четвертых, анимация может длиться разное количество времени, значит мы будем передавать время (в миллисекундах) за которое должна будет проиграться анимация;

в-пятых, объект должен знать откуда и куда должен будет ехать наш див;

У нашего аниматора будет для начала 2 метода, первый - init (в нем мы будем получать входные параметры и запускать анимацию) и второй - move (в нем мы будем собственно двигать наш объект).

Приступим к созданию объекта:

var Animate = { // создаем объект (название объекта принято писать с большой буквы)
init: function(params) { // это наш первый метод, в него передается объект params
/*
* тут "парсим" объект params
* присваиваем дефолтные значения и т.п.
*/

this.move(); // а потом начинаем движение
},

move: function() { // это наш второй метод, тут будем колдовать над анимацией

}
}

Ну вот так примерно будет выглядеть наш аниматор, вызывать который мы будем так:

<input class="button" type="button" value="←" onclick="Animate.init({параметр: значение, параметр: значение, параметр: значение});" />

************************************************************************

Лирическое отступление номер раз

Кому-то может быть непонятна такая запись:

this.x = params.x ? params.x : x;

Так вот это всего-лишь короткий эквивалент вот такой записи:

if (params.x) {
this.x = params.x;
} else {
this.x = x;
}

Некоторые иногда в условии ставят скобки для наглядности:

// скобки в условии для наглядности
this.x = (params.x) ? params.x : x;

************************************************************************

Продолжим создавать наш аниматор. Теперь нам нужно передать аниматору объект который мы собственно собираемся анимировать. Как мы решили это будет или id или нода. Делать мы это будем так:

// для наглядности поставим в условии скобки
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;

Если тип параметра равен string, т.е. если это строка значит мы передали туда id и тогда находим элемент по getElementById(id), если же это не строка, значит мы передали туда объект т.е. ноду, тогда просто передаем ее в параметр.

Далее (согласно нашему плану) нам нужно передать аниматору относительно какой оси у нас будет ездить красный блок. За перемещение по оси X отвечает CSS-атрибут left, а за перемещение по оси Y - атрибут top. По умолчанию будем ездить по X:

this.attr = params.attr ? params.attr : 'left';

Если атрибут задан, то передаем его в объект, иначе передаем в объект значение по умолчанию, в данном случае left.

Аналогично по нашему плану задаем остальные параметры:

// единицы измерения, по умолчанию пиксели
this.units = params.units ? params.units : 'px';
// время анимации, по умолчанию пол секунды
this.duration = params.duration ? params.duration : 500;
// откуда будем ехать, по умолчанию значения не будет
this.from = params.from;
// куда будем ехать, по умолчанию значения не будет
this.to = params.to;

После всех манипуляций наш аниматор должен выглядеть примерно так:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.move();
},

move: function() {

}
}

А вот так мы его будем вызывать:

<input class="button" type="button" value="←" onclick="Animate.init({obj: 'test', from: 450, to: 0});" />

Т.е. при клике на кнопку влево, наш блок поедет справа (с 450 пикселей - 500 минус 50 (ширина синего контейнера минус ширина красного блока)) налево (до 0 пикселей). Остальные параметры можно не задавать т.к. они не являются обязательными и примут дефолтные значения.

Ну вот и все пока. Ждите продолжения, оно обязательно будет.

Вопросы, предложения, критика и грубая лесть, как всегда, приветствуются.

Link to comment
Share on other sites

  • 0

************************************************************************

Лирическое отступление номер два

Вообще это не касается темы данного урока, но камрад s0rr0w верно подметил, что в этом выражении:

this.x = params.x ? params.x : x;

При значении x равном нулю параметр this.x примет дефолтное значение, т.к. 0 и false это одно и то же. Более того, так же интерпретируется и пустая строка. Чтобы убедиться в этом достаточно запустить вот такой код:

alert(0 == false); // выдаст true
alert('' == false); // выдаст true
alert('' == 0); // выдаст true

Я использую вот такой вариант проверки в подобной ситуации:

// скобки в условии для наглядности
this.x = (params.x || !isNaN(parseInt(params.x))) ? params.x : x;

Возможно он немного корявый, но зато работает на все сто.

************************************************************************

Итак, продолжаем строить наш аниматор.

Анимация в JavaScript

Теперь нам необходимо приступать непосредственно к анимации блока. Как же реализовать анимацию на JavaScript, для этого мы можем использовать либо метод объекта window - setTimeout(code, timeout[,lang]), либо метод того же объекта - setInterval(code, timeout[,lang]).

Оба эти метода могут принимать 3 параметра:

code - код или функция, которая будет выполнена через определенный интервал времени;

timeout - временной интервал в миллисекундах, через который будет выполнен код или функция;

lang - необязательный параметр, язык на котором написана функция или код (JScript, VBScript или JavaScript).

Различие между ними в следующем: setTimeout выполняется только один раз, а setInterval выполняется бесконечно, до тех пор пока не будет вызван метод clearInterval(interval_id), который очищает интервал, созданный методом setInterval. Аналогичный метод есть и для таймаута - clearTimeout(timeout_id). В нашем аниматоре мы будем использовать метод setInterval (мне он кажется более удобным).

Итак, как мы и задумали, анимация движения будет происходить в методе move нашего объекта Animate:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.move();
},

move: function() {
this.obj.interval = window.setInterval(someFunc, 10); // какая-то функция someFunc будет вызываться каждые 10 миллисекунд
}
}

Здесь this.obj.interval - это индентификатор интервала, он нужен для того, чтобы по нему мы могли остановить анимацию вызвав метод clearInterval(this.obj.interval) у объекта window. Храним мы наш индентификатор как параметр ноды, которую двигаем, сделано это для того, чтобы мы в любой момент могли обратиться к нему. Так же вы можете установить иной временной интервал, но я методом тыка выяснил, что при таком значении анимация выглядит наиболее естественно. В принципе вы можете добавить дополнительный параметр к нашему объекту, который по дефолту будет равен 10 миллисекунд, но при желании можно будет задать иное значение.

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

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.move();
},

move: function() {
this.obj.interval = window.setInterval(function() {
// производим расчет движения

if (true) { // при наступлении какого-либо условия очищаем интервал, тем самым останавливая анимацию
window.clearInterval(self.obj.interval);
}
}, 10);
}
}

Тут может возникнуть небольшая проблема: так как интервал относится не к нашему объекту, а к объекту window, может возникнуть два интервала (и более) если юзер кликнет на кнопку еще раз до того как анимация будет завершена. В этом случае будет создан еще один интервал, несмотря на то, что свойство interval у ноды this.obj будет перезаписано. Чтобы избежать этого, надо перед созданием нового интервала сначала очистить старый. Вот такой вид примет наш метод с дополнительной проверкой:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.move();
},

move: function() {
if (this.obj.interval) { // если интервал уже существует
window.clearInterval(self.obj.interval); // сначала очищаем его
}

this.obj.interval = window.setInterval(function() { // а потом создаем новый
// производим расчет движения

if (true) { // при наступлении какого-либо условия очищаем интервал, тем самым останавливая анимацию
window.clearInterval(self.obj.interval);
}
}, 10);
}
}

Ну вот и все пока. Продолжение следует.

Про вопросы, предложения, критику и грубую лесть вы уже знаете :blink:

Link to comment
Share on other sites

  • 0

Формула движения

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

x = начальная_координата + (конечная_координата - начальная_координата) * коэфицент;

//где

коэфицент = время_прошедшее_с_начала_анимации / время_за_которое_анимация_должна_закончиться;

Время за которое анимация должна закончиться - это и есть наш параметр this.duration, который, как мы решили, по умолчанию равен 500 миллисекунд или 0,5 секунды. Единственное чего нет в нашем аниматоре, чтобы реализовать эту формулу движения - это свойства, в котором бы хранилось время, прошедшее с начала анимации. Чтобы узнать сколько времени прошло с начала анимации нам надо запомнить какое было время в момент клика по кнопке, для этого мы создадим новое свойство нашего объекта и назовем его, скажем startTime:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.startTime = new Date().getTime(); // запоминаем время начала анимации, в миллисекундах

this.move();
},

move: function() {
if (this.obj.interval) {
window.clearInterval(self.obj.interval);
}

this.obj.interval = window.setInterval(function() {
// производим расчет движения

if (true) {
window.clearInterval(self.obj.interval);
}
}, 10);
}
}

Чтобы узнать сколько времени прошло с начала анимации нам надо внутри интервала вычитать из текущего времени время которое мы запомнили ранее:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.startTime = new Date().getTime(); // запоминаем время начала анимации, в миллисекундах

this.move();
},

move: function() {
if (this.obj.interval) {
window.clearInterval(self.obj.interval);
}

this.obj.interval = window.setInterval(function() {
var elapsedTime = (new Date().getTime()) - this.startTime; // вычитаем из текущего времени время начала анимации

if (true) {
window.clearInterval(self.obj.interval);
}
}, 10);
}
}

Тут мы сталкиваемся с одной проблемой. Дело в том что внутри нашей анонимной функции не будет переменной this.startTime, все это из-за того, что ключевое слово this в данном случае будет ссылаться не на наш объект Animate, а на объект window. Это прекрасно видно если запустить alert:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.startTime = new Date().getTime();

this.move();
},

move: function() {
if (this.obj.interval) {
window.clearInterval(self.obj.interval);
}

this.obj.interval = window.setInterval(function() {
alert(this); // проверяем куда ссылается this

if (true) {
window.clearInterval(self.obj.interval);
}
}, 10);
}
}

Animate.init({obj: 'test', from: 0, to: 0}); // выдаст [object DOMWindow] или что-то вроде того

Чтобы решить эту проблему нужно просто запомнить в переменной ссылку на наш объект, а потом обращаться к свойствам и методам нашего объекта через эту переменную:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.startTime = new Date().getTime();

this.move();
},

move: function() {
var self = this; // запоминаем ссылку на наш объект (принято называть эту переменную self или parent)

if (this.obj.interval) {
window.clearInterval(self.obj.interval);
}

this.obj.interval = window.setInterval(function() {
var elapsedTime = (new Date().getTime()) - self.startTime; // вот теперь все будет работать правильно

if (true) {
window.clearInterval(self.obj.interval);
}
}, 10);
}
}

Теперь все готово для того чтобы реализовать движение нашего блока по формуле которую я привел выше:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.startTime = new Date().getTime();

this.move();
},

move: function() {
var self = this; // запоминаем ссылку на наш объект

if (this.obj.interval) {
window.clearInterval(self.obj.interval);
}

this.obj.interval = window.setInterval(function() {
var elapsedTime = (new Date().getTime()) - self.startTime; // высчитываем время, прошедшее с начала анимации
var k = elapsedTime / self.duration; // высчитываем коэффициент, отношение прошедшего времени анимации ко времени анимации (изменяется от 0 до 1)

var result = self.from + (self.to - self.from) * k;

if (true) {
window.clearInterval(self.obj.interval);
}
}, 10);
}
}

Чтобы остановить анимацию в нужный момент (т.е. через 500 миллисекунд по умолчанию) нам надо проверить прошло ли уже пол секунды, т.е. равно ли отношение elapsedTime / self.duration единице (500 / 500 = 1), а для надежности лучше проверить не больше ли оно единицы:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.startTime = new Date().getTime();

this.move();
},

move: function() {
var self = this; // запоминаем ссылку на наш объект

if (this.obj.interval) {
window.clearInterval(self.obj.interval);
}

this.obj.interval = window.setInterval(function() {
var elapsedTime = (new Date().getTime()) - self.startTime; // высчитываем время, прошедшее с начала анимации
var k = elapsedTime / self.duration; // высчитываем коэффициент, отношение прошедшего времени анимации ко времени анимации (изменяется от 0 до 1)

var result = self.from + (self.to - self.from) * k;

self.obj.style.cssText = self.attr + ': ' + result + self.units; // применяем полученный результат к анимируемому объекту

if (k >= 1) { // если анимация дошла до конца, очищаем интервал, тем самым останавливая анимацию
window.clearInterval(self.obj.interval);
}
}, 10);
}
}

Как вы помните параметр this.attr у нас по умолчанию равен left, а параметр this.units - по умолчанию равен px, соответственно в результате у нас получится такая строка:

self.obj.style.cssText = 'left: ' + result + 'px';

Теперь откройте html-файл с базовой версткой и на кнопки влево и вправо на событие onclick повесте вызов нашего аниматора:

<div class="wrap">
<div class="left">
<!-- Двигаем справа налево -->
<input class="button" type="button" value="←" onclick="Animate.init({obj: 'test', from: 450, to: 0});" />
</div>
<div class="right">
<!-- Двигаем слева направо -->
<input class="button" type="button" value="→" onclick="Animate.init({obj: 'test', from: 0, to: 450});" />
</div>
<div class="container">
<div id="test" class="item"></div>
</div>
</div>

После этого откройте html-файл в вашем любимом браузере и понажимайте стрелки влево и вправо. Вы увидите, что красный блок будет ездить туда-сюда. Один проход блока будет занимать 0,5 секунды. Вы можете попробовать изменить скорость анимации, изменив параметр duration на необходимое значение:

<!-- Двигаем медленнее -->
<div class="wrap">
<div class="left">
<!-- Двигаем справа налево медленнее -->
<input class="button" type="button" value="←" onclick="Animate.init({obj: 'test', from: 450, to: 0, duration: 1000});" />
</div>
<div class="right">
<!-- Двигаем слева направо медленнее -->
<input class="button" type="button" value="→" onclick="Animate.init({obj: 'test', from: 0, to: 450, duration: 1000});" />
</div>
<div class="container">
<div id="test" class="item"></div>
</div>
</div>

<!-- Или двигаем быстрее -->
<div class="wrap">
<div class="left">
<!-- Двигаем справа налево медленнее -->
<input class="button" type="button" value="←" onclick="Animate.init({obj: 'test2', from: 450, to: 0, duration: 250});" />
</div>
<div class="right">
<!-- Двигаем слева направо медленнее -->
<input class="button" type="button" value="→" onclick="Animate.init({obj: 'test2', from: 0, to: 450, duration: 250});" />
</div>
<div class="container">
<div id="test2" class="item"></div>
</div>
</div>

Отлично, все работает. Теперь осталось дело за малым - заставить наш блок двигаться с ускорением и замедлением.

В нашей формуле var result = self.from + (self.to - self.from) * k; коэффициент - это как бы скорость красного блока в определенный момент времени. Чтобы получить движение с ускорением нам нужно чтобы с течением времени скорость становилась больше и больше, для этого будем возводить скорость (т.е. коэффициент k) в квадрат. Чтобы нам было удобней работать с изменениями коэффициента давайте создадим в нашем объекте еще один метод, который назовем например calculate, этот метод будет возвращать скорость возведенную в квадрат:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.startTime = new Date().getTime();

this.move();
},

move: function() {
var self = this;

if (this.obj.interval) {
window.clearInterval(self.obj.interval);
}

this.obj.interval = window.setInterval(function() {
var elapsedTime = (new Date().getTime()) - self.startTime;
var k = elapsedTime / self.duration;

var result = self.from + (self.to - self.from) * self.calculate(k);

self.obj.style.cssText = self.attr + ': ' + result + self.units;

if (k >= 1) {
window.clearInterval(self.obj.interval);
}
}, 10);
},

calculate: function(k) { // создаем новый метод
return Math.pow(k, 2); // который возвращает коэффициент, возведенный в квадрат
}
}

Давайте теперь снова запустим наш html-файл и посмотрим как ведет себя красный блок. Вы увидите что теперь он ездит туда-сюда с ускорением.

Но теперь появилась еще одна небольшая проблема - дело в том, что наш коэффициент k изменяется от 0 до 1, а значит он является дробным числом, из-за этого (если несколько раз понажимать на стрелки) блок иногда немного переезжает за границу голубого блока или недоезжает до нее. Для того чтобы исправить это поведение давайте создадим еще один метод который будет исправлять неверные значения:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.startTime = new Date().getTime();

this.move();
},

move: function() {
var self = this;

if (this.obj.interval) {
window.clearInterval(self.obj.interval);
}

this.obj.interval = window.setInterval(function() {
var elapsedTime = (new Date().getTime()) - self.startTime;
var k = elapsedTime / self.duration;

var result = self.from + (self.to - self.from) * self.calculate(k);

result = self.fixResult(result); // исправляем неверное значение результата

self.obj.style.cssText = self.attr + ': ' + result + self.units;

if (k >= 1) {
window.clearInterval(self.obj.interval);
}
}, 10);
},

calculate: function(k) {
return Math.pow(k, 2);
},

fixResult: function(num) { // новый метод который будет исправлять результат
num = Math.round(num);

if (this.from < this.to) { // если блок едет слева направо
if (num > this.to) {
num = this.to;
}

if (num < this.from) {
num = this.from;
}
} else { // если блок едет справа налево
if (num < this.to) {
num = this.to;
}

if (num > this.from) {
num = this.from;
}
}

return num;
}
}

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

Давайте теперь реализуем движение с замедлением. Для начала давайте создадим еще один параметр у нашего аниматора. При помощи этого параметра мы будем переключать режим в котором будет ездить наш блок (с ускорением или с замедлением). Допустим параметр будет называться type и по умолчанию будет иметь значение accel (от английского acceleration):

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.type = params.type ? params.type : 'accel'; // новый параметр, по умолчанию равен ускорению - 'accel'
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.startTime = new Date().getTime();

this.move();
},

move: function() {
var self = this;

if (this.obj.interval) {
window.clearInterval(self.obj.interval);
}

this.obj.interval = window.setInterval(function() {
var elapsedTime = (new Date().getTime()) - self.startTime;
var k = elapsedTime / self.duration;

var result = self.from + (self.to - self.from) * self.calculate(k);

result = self.fixResult(result);

self.obj.style.cssText = self.attr + ': ' + result + self.units;

if (k >= 1) {
window.clearInterval(self.obj.interval);
}
}, 10);
},

calculate: function(k) {
if (this.type == 'accel') { // если задано движение с ускорением
return Math.pow(k, 2); // возвращаем коэффициент в квадрате
} else { // если вдруг у нас задано иное невалидное значение this.type (например vasya_pupkin)
return k; // тогда просто возвращаем коэффициент и блок поедет без ускорения
}
},

fixResult: function(num) {
num = Math.round(num);

if (this.from < this.to) {
if (num > this.to) {
num = this.to;
}

if (num < this.from) {
num = this.from;
}
} else {
if (num < this.to) {
num = this.to;
}

if (num > this.from) {
num = this.from;
}
}

return num;
}
}

Чтобы реализовать замедление, нам надо совершать действие обратное ускорению, т.е. действие обратное возведению в степень, а именно извлечение квадратного корня. Параметр, который будет отвечать за замедление давайте назовем decel (от английского deceleration):

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.type = params.type ? params.type : 'accel';
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.startTime = new Date().getTime();

this.move();
},

move: function() {
var self = this;

if (this.obj.interval) {
window.clearInterval(self.obj.interval);
}

this.obj.interval = window.setInterval(function() {
var elapsedTime = (new Date().getTime()) - self.startTime;
var k = elapsedTime / self.duration;

var result = self.from + (self.to - self.from) * self.calculate(k);

result = self.fixResult(result);

self.obj.style.cssText = self.attr + ': ' + result + self.units;

if (k >= 1) {
window.clearInterval(self.obj.interval);
}
}, 10);
},

calculate: function(k) {
if (this.type == 'accel') { // если задано движение с ускорением
return Math.pow(k, 2); // возвращаем коэффициент в квадрате
} else if (this.type == 'decel') { // если задано движение с замедлением
return Math.sqrt(k); // возвращаем квадратный корень из коэффициента
} else { // если вдруг у нас задано невалидное значение this.type
return k; // тогда просто возвращаем коэффициент
}
},

fixResult: function(num) {
num = Math.round(num);

if (this.from < this.to) {
if (num > this.to) {
num = this.to;
}

if (num < this.from) {
num = this.from;
}
} else {
if (num < this.to) {
num = this.to;
}

if (num > this.from) {
num = this.from;
}
}

return num;
}
}

Давайте попробуем все в действии:

<!-- Двигаемся с ускорением -->
<div class="wrap">
<div class="left">
<input class="button" type="button" value="←" onclick="Animate.init({obj: 'test', from: 450, to: 0, type: 'accel'});" />
</div>
<div class="right">
<input class="button" type="button" value="→" onclick="Animate.init({obj: 'test', from: 0, to: 450, type: 'accel'});" />
</div>
<div class="container">
<div id="test" class="item"></div>
</div>
</div>

<!-- Двигаемся с замедлением -->
<div class="wrap">
<div class="left">
<input class="button" type="button" value="←" onclick="Animate.init({obj: 'test2', from: 450, to: 0, type: 'decel'});" />
</div>
<div class="right">
<input class="button" type="button" value="→" onclick="Animate.init({obj: 'test2', from: 0, to: 450, type: 'decel'});" />
</div>
<div class="container">
<div id="test2" class="item"></div>
</div>
</div>

Но самый интересный эффект - это движение сначала с ускорение, а потом с замедлением. Именно так движутся объекты в жизни, поэтому данный эффект выглядит наиболее естественно. Тут мои познания в математике заканчиваются и приходится обращаться к гуглю, вот какую формулу я нагуглил:

Math.pow(k, 2) * (3 - 2 * k);

Давайте применим ее. Параметр назовем both:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.type = params.type ? params.type : 'accel';
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.startTime = new Date().getTime();

this.move();
},

move: function() {
var self = this;

if (this.obj.interval) {
window.clearInterval(self.obj.interval);
}

this.obj.interval = window.setInterval(function() {
var elapsedTime = (new Date().getTime()) - self.startTime;
var k = elapsedTime / self.duration;

var result = self.from + (self.to - self.from) * self.calculate(k);

result = self.fixResult(result);

self.obj.style.cssText = self.attr + ': ' + result + self.units;

if (k >= 1) {
window.clearInterval(self.obj.interval);
}
}, 10);
},

calculate: function(k) {
if (this.type == 'accel') {
return Math.pow(k, 2);
} else if (this.type == 'decel') {
return Math.sqrt(k);
} else if (this.type == 'both') { // если параметр равен both
return Math.pow(k, 2) * (3 - 2 * k); // движемся сначала с ускорением, а потом с замедлением
} else {
return k;
}
},

fixResult: function(num) {
num = Math.round(num);

if (this.from < this.to) {
if (num > this.to) {
num = this.to;
}

if (num < this.from) {
num = this.from;
}
} else {
if (num < this.to) {
num = this.to;
}

if (num > this.from) {
num = this.from;
}
}

return num;
}
}

Ну вот мы и реализовали все базовые виды анимации объекта в нашем аниматоре. В принципе, для ускорения не обязательно возводить коэффициент в квадрат, если возводить его в куб (или в четверную степень и т.д.), то ускорение будет более резким. То же само е можно применить и к замедлению - для более выраженного эффекта надо извлекать кубический корень (или корень червертой степени), для этого нужно возводить коэффициент в степень 1/3. Я ввел еще один параметр в аниматор, чтобы иметь возможность изменять выраженность эффектов ускорения и замедления:

var Animate = {
init: function(params) {
this.obj = (typeof params.obj == 'string') ? document.getElementById(params.obj) : params.obj;
this.power = params.power ? params.power : 2;
this.type = params.type ? params.type : 'accel';
this.attr = params.attr ? params.attr : 'left';
this.units = params.units ? params.units : 'px';
this.duration = params.duration ? params.duration : 500;
this.from = params.from;
this.to = params.to;

this.startTime = new Date().getTime();

this.move();
},

move: function() {
var self = this;

if (this.obj.interval) {
window.clearInterval(self.obj.interval);
}

this.obj.interval = window.setInterval(function() {
var elapsedTime = (new Date().getTime()) - self.startTime;
var k = elapsedTime / self.duration;

var result = self.from + (self.to - self.from) * self.calculate(k);

result = self.fixResult(result);

self.obj.style.cssText = self.attr + ': ' + result + self.units;

if (k >= 1) {
window.clearInterval(self.obj.interval);
}
}, 10);
},

calculate: function(k) {
if (this.type == 'accel') {
return Math.pow(k, this.power);
} else if (this.type == 'decel') {
return Math.pow(k, 1 / this.power);
} else if (this.type == 'both') {
return Math.pow(k, this.power) * (3 - 2 * k);
} else {
return k;
}
},

fixResult: function(num) {
num = Math.round(num);

if (this.from < this.to) {
if (num > this.to) {
num = this.to;
}

if (num < this.from) {
num = this.from;
}
} else {
if (num < this.to) {
num = this.to;
}

if (num > this.from) {
num = this.from;
}
}

return num;
}
}

Ну вот мы и реализовали наш мини-фреймворк для анимации движения объектов. Что он умеет:

1) анимировать движение объекта по оси X или Y;

2) анимировать движение объекта с эффектами ускорения и замедления;

3) анимировать движения в пикселях или в процентах;

4) время анимации может изменяться по желанию пользователя;

5) аниматор имеет всего 3 обязательных параметра - id ноды или сама нода, начальная координата и конечная координата.

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

Спасибо за внимание. Про вопросы, предложения, критику и лесть вы помните.

P.S.

Сайт, на котором я нашел основную формулу и формулу движения с ускорением и замедлением (easeInOut) - тут

  • Like 3
Link to comment
Share on other sites

Guest
This topic is now closed to further replies.
 Share

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. See more about our Guidelines and Privacy Policy