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'е и мы можем приступать собственно к построению самого фреймворка.
Что почитать на тему:
https://javascript.ru/tutorial/object
https://designformast...ing-javascript/
«javascript ООП» в Google.
Итак пришло время создать тестовую страничку, над которой мы будем экспериментировать:
<html xmlns="https://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.
Создаем базовый объект
Итак нам надо продумать что должен уметь наш аниматор:
во-первых, он должен уметь находить объект по id если ему передали id, так же было бы неплохо если бы ему можно было бы передать ноду вместо id (это иногда удобней);
во-вторых, он должен уметь двигать объект по оси X или по оси Y, в зависимости от параметра который мы передадим ему;
в-третьих, неплохо было бы передавать ему разные единицы измерения (пиксели, проценты);
в-четвертых, анимация может длиться разное количество времени, значит мы будем передавать время (в миллисекундах) за которое должна будет проиграться анимация;
в-пятых, объект должен знать откуда и куда должен будет ехать наш див;
|
У нашего аниматора будет для начала 2 метода, первый - init (в нем мы будем получать входные параметры и запускать анимацию) и второй - move (в нем мы будем собственно двигать наш объект).
Приступим к созданию объекта:
init: function(params) { // это наш первый метод, в него передается объект params
/*
* тут "парсим" объект params
* присваиваем дефолтные значения и т.п.
*/
this.move(); // а потом начинаем движение
},
move: function() { // это наш второй метод, тут будем колдовать над анимацией
}
}
Ну вот так примерно будет выглядеть наш аниматор, вызывать который мы будем так:
************************************************************************
Лирическое отступление номер раз
Кому-то может быть непонятна такая запись:
Так вот это всего-лишь короткий эквивалент вот такой записи:
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:
Если атрибут задан, то передаем его в объект, иначе передаем в объект значение по умолчанию, в данном случае left.
Аналогично по нашему плану задаем остальные параметры:
this.units = params.units? params.units: 'px';
// время анимации, по умолчанию пол секунды
this.duration = params.duration? params.duration: 500;
// откуда будем ехать, по умолчанию значения не будет
this.from = params.from;
// куда будем ехать, по умолчанию значения не будет
this.to = params.to;
После всех манипуляций наш аниматор должен выглядеть примерно так:
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 пикселей). Остальные параметры можно не задавать т.к. они не являются обязательными и примут дефолтные значения.
Ну вот и все пока. Ждите продолжения, оно обязательно будет.
Вопросы, предложения, критика и грубая лесть, как всегда, приветствуются.
Публикуюсь тут - css-live.ru
#4 s0rr0w
·
· Бестолочь
·
·
·
· Группа: Глобальный модератор
· Сообщений: 866 4
· Регистрация: 11 Август 06
· Пол:
· Репутация: 167 (Хорошая)
Отправлено 18 Май 2010 - 16:26
this.x = params.x? params.x: x;
При такой записи, если params.x = 0, присвоится второе значение - x.
#5 Great Rash
·
· Лошара
·
·
·
· Группа: Модератор
· Сообщений: 015 5
· Регистрация: 09 Февраль 09
· Пол:
· Репутация: 276 (Хорошая)
Отправлено 20 Май 2010 - 11:29
************************************************************************
Лирическое отступление номер два
Вообще это не касается темы данного урока, но камрад s0rr0w верно подметил, что в этом выражении:
При значении x равном нулю параметр this.x примет дефолтное значение, т.к. 0 и false это одно и то же. Более того, так же интерпретируется и пустая строка. Чтобы убедиться в этом достаточно запустить вот такой код:
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:
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. Можно конечно создать еще один метод в нашем аниматоре и потом вызывать его, но я предпочитаю создать в интервале анонимную функцию в которой и будут производиться все расчеты. Вот так примерно будет выглядеть скелет этой анонимной функции:
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 будет перезаписано. Чтобы избежать этого, надо перед созданием нового интервала сначала очистить старый. Вот такой вид примет наш метод с дополнительной проверкой:
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);
}
}
Ну вот и все пока. Продолжение следует.
Про вопросы, предложения, критику и грубую лесть вы уже знаете
Публикуюсь тут - css-live.ru
#6 Great Rash
·
· Лошара
·
·
·
· Группа: Модератор
· Сообщений: 015 5
· Регистрация: 09 Февраль 09
· Пол:
· Репутация: 276 (Хорошая)
Отправлено 24 Май 2010 - 10:19
Формула движения
Так как мы будем делать движение не просто увеличивая координату, а движение с ускорением и замедлением, то нам понадобится некая формула движения. Я не математик, более того, с математикой я совсем не дружу, так что формулу расчета движения я искал очень долго и упорно гугля. И совершенно случайно наткнулся на один пост в форуме где эта формула приведена:
//где
коэфицент = время_прошедшее_с_начала_анимации / время_за_которое_анимация_должна_закончиться;
Время за которое анимация должна закончиться - это и есть наш параметр this.duration, который, как мы решили, по умолчанию равен 500 миллисекунд или 0,5 секунды. Единственное чего нет в нашем аниматоре, чтобы реализовать эту формулу движения - это свойства, в котором бы хранилось время, прошедшее с начала анимации. Чтобы узнать сколько времени прошло с начала анимации нам надо запомнить какое было время в момент клика по кнопке, для этого мы создадим новое свойство нашего объекта и назовем его, скажем startTime:
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);
}
}
Чтобы узнать сколько времени прошло с начала анимации нам надо внутри интервала вычитать из текущего времени время которое мы запомнили ранее:
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:
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] или что-то вроде того
Чтобы решить эту проблему нужно просто запомнить в переменной ссылку на наш объект, а потом обращаться к свойствам и методам нашего объекта через эту переменную:
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);
}
}
Теперь все готово для того чтобы реализовать движение нашего блока по формуле которую я привел выше:
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), а для надежности лучше проверить не больше ли оно единицы:
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, соответственно в результате у нас получится такая строка:
Теперь откройте html-файл с базовой версткой и на кнопки влево и вправо на событие onclick повесте вызов нашего аниматора:
<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, этот метод будет возвращать скорость возведенную в квадрат:
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, а значит он является дробным числом, из-за этого (если несколько раз понажимать на стрелки) блок иногда немного переезжает за границу голубого блока или недоезжает до нее. Для того чтобы исправить это поведение давайте создадим еще один метод который будет исправлять неверные значения:
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):
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):
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>
Но самый интересный эффект - это движение сначала с ускорение, а потом с замедлением. Именно так движутся объекты в жизни, поэтому данный эффект выглядит наиболее естественно. Тут мои познания в математике заканчиваются и приходится обращаться к гуглю, вот какую формулу я нагуглил:
Давайте применим ее. Параметр назовем both:
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. Я ввел еще один параметр в аниматор, чтобы иметь возможность изменять выраженность эффектов ускорения и замедления:
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) - тут
Установка веб-сервера Apache и средств программирования под Windows
Шаг 1 - Загрузка компонентов
На время написания статьи последними стабильными версиями (бывают еще тестовые или бета-версии, они весьма ненадежны, так что смотрите что скачиваете) были:
- Apache 2.2.11
- PHP 5.2.8
- Установка производилась на Windows Vista Business Service Pack 1 и XP (2002) Professional Service Pack 3
Эти данные важны потому что от версии к версии, увы, не всегда все одинаково; программное обеспечение имеет свойство развиваться (но и справочные материалы к нему тоже). В первую очередь нам нужен веб-сервер. Он послужит для имитации полноценного хостинга. Как я уже говорил, практически повсеместно сегодня используется Apache HTTP Server. Скачать его можно отсюда: https://httpd.apache.org/download.cgi. Вам предлагаются для скачивания несколько вариантов. Нас интересует «Win32 Binary without crypto (no mod_ssl) (MSI Installer)». Немного разъяснений: