Для начала вспомним, для чего нам нужны плагины? Мой ответ — создание повторно используемого кода, и да — с удобным интерфейсом. Давайте напишем такой код, вот простая задачка: "По клику на параграф, текст должен измениться на красный"
Дабы не забывать истоков — начнем с реализации на нативном JavaScript'е:
var loader = function () {
// находим все параграфы
var para = document.getElementsByTagName('P');
// перебираем все, и вешаем обработчик
for (var i=0,size=para.length;i<size;i++) {
// обработчик
para[i].onclick = function() {
this.style.color = "#FF0000";
}
}
}
// естественно, весь код должен работать после загрузки всей страницы
document.addEventListener("DOMContentLoaded", loader, false);
Данный код не является кроссбраузерным, и написан с целью лишний раз подчеркнуть удобство использования библиотеки ;)
Теперь можно этот код упростить, подключаем jQuery и получаем следующий вариант:
$(function(){
$('p').click(function(){
$(this).css('color', '#ff0000');
})
});
С поставленной задачей мы справились, но где тут повторное использование
кода? Или если нам надо не в красный, а в зеленый перекрасить? Вот тут
начинается самое интересное, чтобы написать простой плагин достаточно
расширить объект $.fn:
$.fn.mySimplePlugin = function() {
$(this).click(function(){
$(this).css('color', '#ff0000');
})
}
Если же писать более грамотно, то нам необходимо ограничить переменную $ только нашим плагином, а так же возвращать "this", чтобы можно было использовать цепочки вызовов (т.н. "chaining"), делается это следующим образом:
(function($) {
$.fn.mySimplePlugin = function(){
// код плагина
return this;
};
})(jQuery);
Внесу небольшое пояснение о происходящей тут "магии", код
"(function($){…})(jQuery)" создает анонимную функцию, и тут же
вызывает ее, передавая в качестве параметра объект jQuery, таким
образом внутри анонимной функции мы можем использовать алиас $
не боясь за конфликты с другими библиотеками — так как теперь $
находится лишь в области видимости нашей функции, и мы имеем
полный контроль над ней. Если у вас возникло ощущение дежавю – то
всё верно, я об этом уже рассказывал
Добавим опцию по выбору цвета и получим рабочий плагин:
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример плагина с лобальными настройками</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/code.js"></script>
<script type="text/javascript">
(function($) {
// значени по умолчанию
var defaults = {
color:'green'
};
// актуальные настройки, глобальные
var options;
$.fn.mySimplePlugin = function(params){
// при многократном вызове функции настройки будут сохранятся, и замещаться при необходимости
options = $.extend({}, defaults, options, params);
$(this).click(function(){
$(this).css('color', options.color);
});
return this;
};
})(jQuery);
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu>
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
<hr/>
<pre><code>$(<span>'p:eq(0)'</span>).mySimplePlugin()</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'p:eq(1)'</span>).mySimplePlugin({'color':'red'})</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'p:eq(2)'</span>).mySimplePlugin({'color':'blue'})</code></pre>
<button type="button" class="code">Run Code</button>
</menu>
<header>
<h1>Пример плагина с глобальными настройками</h1>
<h2>Запустите плагин с новыми настройками и проклацайте все параграфы</h2>
</header>
<article>
<h2>Article Title</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
</article>
<article>
<h2>Article Title</h2>
<p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
libero. Fusce et ultrices lectus.</p>
</article>
<article>
<h2>Article</h2>
<p>Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
(function($) {
// значение по умолчанию - ЗЕЛЁНЫЙ
var defaults = { color:'green' };
// актуальные настройки, глобальные
var options;
$.fn.mySimplePlugin = function(params){
// при многократном вызове настройки будут сохранятся
// и замещаться при необходимости
options = $.extend({}, defaults, options, params);
$(this).click(function(){
$(this).css('color', options.color);
});
return this;
};
})(jQuery);
Вызов:
// первый вызов
$('p:first,p:last').mySimplePlugin();
// второй вызов
$('p:eq(1)').mySimplePlugin({ color: 'red' });
В результате работы данного плагина, каждый клик будет изменять цвет параграфа на красный, т.к. мы используем глобальную переменную для хранения настроек, то второй вызов плагина изменят значение для всех элементов.
Можно внести небольшие изменения, и разделить настройки для каждого вызова:
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример плагина с индивидуальными настройками</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/code.js"></script>
<script type="text/javascript">
(function($) {
// значени по умолчанию
var defaults = {
color:'green'
};
$.fn.mySimplePlugin = function(params){
// актуальные настройки, будут индивидуальными при каждом запуске
var options = $.extend({}, defaults, options, params);
$(this).click(function(){
$(this).css('color', options.color);
});
return this;
};
})(jQuery);
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu>
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
<hr/>
<pre><code>$(<span>'p:eq(0)'</span>).mySimplePlugin()</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'p:eq(1)'</span>).mySimplePlugin({'color':'red'})</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'p:eq(2)'</span>).mySimplePlugin({'color':'blue'})</code></pre>
<button type="button" class="code">Run Code</button>
</menu>
<header>
<h1>Пример плагина с индивидуальными настройками</h1>
<h2>Разница лишь в одном объявлении var</h2>
</header>
<article>
<h2>Article Title</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
</article>
<article>
<h2>Article Title</h2>
<p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
libero. Fusce et ultrices lectus.</p>
</article>
<article>
<h2>Article</h2>
<p>Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
// актуальные настройки, будут индивидуальными при каждом запуске
var options = $.extend({}, defaults, params);
А разница то в одном "var". Мне даже сложно себе представить как много часов убито в поисках потерянного "var" в JavaScript'е, будьте внимательны
Тут все просто, достаточно запомнить — "this" содержит jQuery объект с коллекцией всех элементов, т.е.:
$.fn.mySimplePlugin = function(){
console.log(this); // это jQuery объект
console.log(this.length); // число элементов в выборке
};
Если мы хотим обрабатывать каждый элемент то соорудим следующую конструкцию внутри нашего плагина:
// необходимо обработать каждый элемент в коллекции
return this.each(function(){
$(this).click(function(){
$(this).css('color', options.color);
});
});
// предыдущий вариант немного избыточен,
// т.к. внутри функции click и так есть перебор элементов
return this.click(function(){
$(this).css('color', options.color);
});
Опять же напомню, если ваш плагин не должен что-то возвращать по вашей задумке — возвращайте "this" — цепочки вызовов в jQuery это часть магии, не стоит её разрушать. Методы "each()" и "click()" возвращают объект jQuery.
Так, у нас написан крутой плагин, надо бы ему еще докрутить функционала, пусть цвет регулируется несколькими кнопками на сайте. Для этого нам понадобится некий метод "color", который и будет в ответе за всё. Сейчас приведу пример кода готового плагина — будем курить вместе (обращайте внимание на комментарии):
// настройки со значением по умолчанию
var defaults = { color:'green' };
// наши будущие публичные методы
var methods = {
// инициализация плагина
init: function(params) {
// настройки, будут индивидуальными при каждом запуске
var options = $.extend({}, defaults, params);
// инициализируем лишь единожды
if (!this.data('mySimplePlugin')) {
// закинем настройки в реестр data
this.data('mySimplePlugin', options);
// добавим событий
this.bind('click.mySimplePlugin', function(){
$(this).css('color', options.color);
});
}
return this;
},
// изменяем цвет в реестре
color: function(color) {
var options = $(this).data('mySimplePlugin');
options.color = color;
$(this).data('mySimplePlugin', options);
},
// сброс цвета элементов
reset: function() {
$(this).css('color', 'black');
}
};
$.fn.mySimplePlugin = function(method){
// немного магии
if ( methods[method] ) {
// если запрашиваемый метод существует, мы его вызываем
// все параметры, кроме имени метода прийдут в метод
// this так же перекочует в метод
return methods[ method ].apply( this, Array.prototype.slice.call(
arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
// если первым параметром идет объект, либо совсем пусто
// выполняем метод init
return methods.init.apply( this, arguments );
} else {
// если ничего не получилось
$.error('Метод "' + method + '" в плагине не найден');
}
};
Теперь еще небольшой пример использование данных методов:
// вызов без параметров - будет вызван init
$('p').mySimplePlugin();
// вызов метода color и передача цвета в качестве параметров
$('p').mySimplePlugin('color', '#FFFF00');
// вызов метода reset
$('p').mySimplePlugin('reset');
Для понимания данного кусочка кода, вы должны разобраться лишь с переменной "arguments", и с методом "apply()". Тут им целые статьи посвятили, дерзайте:
Если ваш плагин вешает какой-либо обработчик, то лучше всего (читай всегда) данный обработчик повесить в своём собственном namespace:
return this.bind("click.mySimplePlugin", function(){
$(this).css('color', options.color);
});
Данный финт позволит в любой момент убрать все ваши обработчики, или вызвать только ваш, что очень удобно:
// вызовем лишь наш обработчик
$('p').trigger("click.mySimplePlugin");
// убираем все наши обработчики
$('p').unbind(".mySimplePlugin");
Дежавю? Ок!
На этом об обычных плагинах всё, хотя дам ещё чуток информации к размышлению, но на английском:
Если по какой-то причине вы еще не знакомы с "data()" — то советую прочитать документацию и усвоить незамедлительно. Если же в двух словах — это реестр данных, и все данные привязанные к какому-либо элементу лучше хранить в нём, это же правило касается и плагинов. Если вам надо сохранить состояние плагина — используйте "data()", если необходим кеш — используйте "data()", если вам необходимо сохранить … ну думаю понятно. Приведу еще примерчик связанный с инициализацией:
function() { // функция init
var init = $(this).data('mySimplePlugin');
if (init) {
return this;
} else {
$(this).data('mySimplePlugin', true);
return this.bind('click.mySimplePlugin', function(){
$(this).css('color', options.color);
});
}
}
По стечению обстоятельств, в HTML5 появились data-атрибуты, и для доступа к ним jQuery использует тот же метод "data()", но вот дела, "jQuery.data()" – не манипулирует атрибутами HTML, а работает со своим реестром, и лишь при отсутствии там данных пытается заполучить атрибут "data-*", не попадитесь:
<div id="my" data-foo="bar"></div>
$("#my").data("foo"); // >>> bar
$("#my").attr("data-foo"); // >>> bar
$("#my").data("foo", "xyz");
$("#my").data("foo"); // >>> xyz
$("#my").attr("data-foo"); // >>> bar
$("#my").attr("data-foo", "def");
$("#my").data("foo"); // >>> xyz
$("#my").attr("data-foo"); // >>> def
<div id="my" data-foo="def"></div>
Информация в данном разделе актуальна для jQuery версии 1.8 и выше, если вас заинтересуют возможности расширения для более старых версий, то читайте мою статью "Пишем плагины анимации"
Для начала затравка – метод "animate()" манипулирует объектом "jQuery.Animation", который предусматривает следующие точки для расширения функционала:
Начну рассказ с "jQuery.Tween.propHooks", т.к. уже есть плагины, в код которых можно заглянуть :) Для пущей наглядности я возьму достаточно тривиальную задачу – заставим плавно изменить цвет шрифта для заданного набора элементов:
$('p').animate({color:'#ff0000'});
Приведенный выше код не даст никакого эффекта, т.к. свойство "color" библиотека из коробки не анимирует, но это можно исправить – надо лишь прокачать "jQuery.Tween.propHooks":
$.Tween.propHooks.color = {
get: function(tween) {
return tween.elem.style.color;
}
set: function(tween) {
tween.easing; // текущий easing
tween.elem; // испытуемый элемент
tween.options; // настройки анимация
tween.pos; // текущий прогресс
tween.prop; // свойство которое изменяем
tween.start; // начальные значения
tween.now; // текущее значение
tween.end; // желаемое результирующие значения
tween.unit; // еденицы измерения
}
}
Этот код ещё не работает, это наши кирпичики, с их помощью будем строить нашу анимацию. Перед работой стоит заглянуть внутрь каждого из приведенных свойств:
console.log(tween);
>>>
easing: "swing"
elem: HTMLParagraphElement
end: "#ff0000"
now: "NaNrgb(0, 0, 0)"
options: Object
complete: function (){}
duration: 1000
old: false
queue: "fx"
specialEasing: Object
pos: 1
prop: "color"
start: "rgb(0, 0, 0)"
unit: "px"
В консоле у нас будет очень много данных, т.к. приведённый метод вызывается N кол-во раз, в зависимости от продолжительности анимации, при этом "tween.pos" постепенно наращивает своё значение с 0 до 1. По умолчанию, наращивание происходит линейно, если надо как-то иначе — то стоит посмотретьли дочитать раздел до конца (об этом я уже упоминал в лекции Анимация)
Даже при таком раскладе мы уже можем изменять выбранный элемент (путём манипуляций над "tween.elem"), но есть более удобный способ – можно установить свойство "run" объекта "tween":
$.Tween.propHooks.color = {
set: function(tween) {
// тут будет инициализация
tween.run = function(progress) {
// тут код отвечающий за измение свойств элемента
}
}
}
Получившийся код будет работать следующим образом:
Теперь, возвращаясь к изначальной задачи по изменению цвета можно наворотить следующий код:
$.Tween.propHooks.color = {
set: function(tween) {
// приводим начальное и конечное значения к единому формату
// #FF0000 == [255,0,0]
tween.start = parseColor(tween.start);
tween.end = parseColor(tween.end);
tween.run = function(progress) {
tween.elem.style['color'] =
// вычисляем промежуточное значение
buildColor(tween.start, tween.end, progress);
}
}
}
Код функций "parseColor()" и "buildColor()" вы найдёте в листинге на странице color.html
Результатом станет плавное перетекание исходного цвета к красному (#F00 == #FF0000 == 255,0,0), вживую можно посмотреть на странице со следующим кодом
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример расширения анимации</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript">
/**
* @author Anton Shevchuk (AntonShevchuk@gmail.com)
* @uri http://anton.shevchuk.name
*/
(function($) {
/**
* parse HEX color to DEC
* e.g. #f00 => [255,0,0]
* #ff00ff => [255,0,255]
* @param {Array} color
*/
function parseColor(color) {
if (color.length != 4
color.length != 7) {
return [0,0,0];
}
color = color.substr(1);
if (color.length == 3) {
color = color[0]+color[0]+
color[1]+color[1]+
color[2]+color[2];
}
var R = parseInt(color.substr(0,2),16);
var G = parseInt(color.substr(2,2),16);
var B = parseInt(color.substr(4,2),16);
return [R,G,B];
};
/**
* Build color for apply as style
*/
function buildColor(start, end, progress) {
// нехитрое вычисление
var R = Math.round(((end[0] - start[0]) * progress) + start[0]);
var G = Math.round(((end[1] - start[1]) * progress) + start[1]);
var B = Math.round(((end[2] - start[2]) * progress) + start[2]);
return 'rgb('+R+','+G+','+B+')';
}
$.Tween.propHooks.color = {
set: function(tween) {
// console.log(tween);
// испытуемый элемент
tween.elem;
// текущий easing
tween.easing
// настройки анимация
tween.options;
// текущий прогресс
tween.pos;
// свойство которое изменяем
tween.prop;
// начальные значения
tween.start = parseColor(tween.start);
// текущее значение
tween.now;
// результирующие значения
tween.end = parseColor(tween.end);
// еденицы измерения
tween.unit;
tween.run = function(progress) {
// непосредственно измение свойств элемента
tween.elem.style['color'] = buildColor(tween.start, tween.end, progress);
}
}
}
})(jQuery);
$(function(){
$('p:eq(0)').animate({color:'#ff0000'},1000);
$('p:eq(1)').animate({color:'#ff0000'},3000);
$('p:eq(2)').animate({color:'#ff0000'},5000);
});
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu>
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
</menu>
<header>
<h1>Пример расширения анимации</h1>
<h2>Данный пример работает лишь с jQuery 1.8+</h2>
</header>
<article>
<h2>Article Title</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
</article>
<article>
<h2>Article Title</h2>
<p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
libero. Fusce et ultrices lectus.</p>
</article>
<article>
<h2>Article</h2>
<p>Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
В плагине jQuery Color для решения поставленной задачи использовали jQuery.cssHooks, но мы же не ищем лёгких путей.
Ещё хотел было рассказать про префильтры анимации, но – документации нет, а как использовать "в жизни" – я не догадался, но чуть-чуть информации таки накопал (код можно найти в функции "Animation"):
jQuery.Animation.prefilter(function(element, props, opts) {
// deffered объект animate
element; // искомый элемент
props; // настройки анимации из animate()
opts; // опции анимации
// отключаем анимацию при попытке анимировать высоту элемента
if (props['height'] != undefined) {
return this;
}
});
Пример:
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример расширения animate через префильтры</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/code.js"></script>
<script>
// jQuery.Animation.preFilter( function( element, props, opts ) {})
jQuery.Animation.prefilter(function(element, props, opts) {
//console.log(this, element, props, opts);
// disable "height" animation
if (props['height'] != undefined) {
return this;
}
});
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu label="Try...">
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
<hr/>
<pre><code contenteditable="true">$(<span>'img'</span>).animate({
<span>'width'</span>:<span>'+=20px'</span>
}, 2000)</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code contenteditable="true"><em>// disabled</em>
$(<span>'img'</span>).animate({
<span>'height'</span>:<span>'+=20px'</span>
}, 2000)</code></pre>
<button type="button" class="code">Run Code</button>
</menu>
<header>
<h1>Анимация</h1>
<h2>Пример расширения через префильтры</h2>
</header>
<article id="stick" class="box">
<h2>Article</h2>
<p>
<img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200"/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
Про "jQuery.Animation.tweener" так же много не расскажешь, но пример получилось сделать чуток интересней – приведённый код позволяет анимировать ширину и высоту объекта по заданной диагонали:
Осторожно, для понимания происходящего потребуются знания геометрии за 8-ой класс
// создаём поддержку нового свойства для анимации – diagonal
jQuery.Animation.tweener( "diagonal", function( property, value ) {
// создаём tween объект
var tween = this.createTween( property, value );
// промежуточные вычисления и данные
var a = jQuery.css(tween.elem, 'width', true );
var b = jQuery.css(tween.elem, 'height', true );
var c = Math.sqrt(a*a + b*b), sinA = a/c, sinB = b/c;
tween.start = c;
tween.end = value;
tween.run = function(progress) {
// вычисление искомого значения – новое значение гипотенузы
var hyp = this.start + ((this.end - this.start) * progress);
// непосредственно измение свойств элемента
tween.elem.style.width = sinA*hyp + tween.unit; // ширина
tween.elem.style.height = sinB*hyp + tween.unit; // высота
};
return tween;
});
Пример работы:
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример расширения animate через tweener</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/code.js"></script>
<script>
// jQuery.Animation.tweener( propsList, function( property, value ) {})
jQuery.Animation.tweener( "diagonal", function( property, value ) {
var tween = this.createTween( property, value );
var target = tween.cur();
var elem = tween.elem;
/*
A
|\
b | \ c
| \
|___\
C a B
*/
var a = jQuery.css( elem, 'width', true );
var b = jQuery.css( elem, 'height', true );
var c = Math.sqrt(a*a + b*b);
var sinA = a/c;
var sinB = b/c;
appendOut('Hypotenuse (start): '+c+'\n');
tween.start = c;
tween.end = value;
tween.run = function(progress) {
// вычисление искомого значения
var newc = this.start + ((this.end - this.start) * progress);
var width = sinA*newc;
var height = sinB*newc;
// непосредственно измение свойств элемента
if (progress == 1) {
width = Math.ceil(width);
height = Math.ceil(height);
appendOut('Hypotenuse (finish): '+Math.sqrt(width*width + height*height)+'\n');
}
tween.elem.style.width = width + tween.unit;
tween.elem.style.height = height + tween.unit;
};
return tween;
});
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu label="Try...">
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
<hr/>
<pre><code contenteditable="true">$(<span>'img'</span>).animate({
<span>'diagonal'</span>:400
}, 2000)</code></pre>
<button type="button" class="code">Run Code</button>
</menu>
<div id="output">
<h3>Output</h3>
<pre></pre>
</div>
<header>
<h1><a href="index.html">Анимация</a></h1>
<h2>Пример расширения анимации через tweener</h2>
</header>
<article id="stick" class="box">
<h2>Article</h2>
<p>
<img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200" />
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
Теперь опять обратимся к easing’у – приведу пример произвольной функции, которой будет следовать анимация. Дабы особо не фантазировать – я взял пример из статьи на вездесущем хабре o анимации в MooTools фреймворке [http://habrahabr.ru/post/43379/] – наглядный пример с сердцебиением, которое описывается следующими функциями:
В расширении функционала easing нет ничего военного:
$.extend($.easing, {
/**
* Heart Beat
*
* @param x progress
* @param t current time
* @param b = 0
* @param c = 1
* @param d duration
* @link http://habrahabr.ru/blogs/mootools/43379/
*/
heart:function(x, t, b, c, d) {
if (x < 0.3) return Math.pow(x, 4) * 49.4;
if (x < 0.4) return 9 * x - 2.3;
if (x < 0.5) return -13 * x + 6.5;
if (x < 0.6) return 4 * x - 2;
if (x < 0.7) return 0.4;
if (x < 0.75) return 4 * x - 2.4;
if (x < 0.8) return -4 * x + 3.6;
if (x >= 0.8) return 1 - Math.sin(Math.acos(x));
}
});
Чуть-чуть пояснений, конструкция "$.extend({}, {})" "смешивает" объекты:
$.extend({name:"Anton"}, {location:"Kharkiv"});
>>>
{
name:"Anton",
location:"Kharkiv"
}
$.extend({name:"Anton", location:"Kharkiv"}, {location:"Kyiv"});
>>>
{
name:"Anton",
location:"Kyiv"
}
Таким образом мы "вмешиваем" новый метод к существующему объекту "$.easing"; согласно коду, наш метод принимает в качестве параметров следующие значения:
Результат конечно интересен, но его можно ещё чуть-чуть расширить дополнительными функциями (развернем и скомбинируем):
heartIn: function (x, t, b, c, d) {
return $.easing.heart(x, t, b, c, d);
},
heartOut: function (x, t, b, c, d) {
return c - $.easing.heart(1 - x, t, b, c, d) + b;
},
heartInOut: function (x, t, b, c, d) {
if (t < d/2) return $.easing.heartIn(x, t, b, c, d);
return $.easing.heartOut(x, t, b, c, d);
}
Получим следующие производные функции:
Работать с данным творением надо следующим образом:
$("#my").animate({height:"+200px"}, 2000, "heartIn"); // вот оно
Пример работы данной функции можно пощупать на примере
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример расширения объекта easing</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<style type="text/css">
.heart{
width:200px;
height:200px;
background:#fff;
margin:0 auto;
overflow:hidden;
position: relative;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
margin-bottom: 4px;;
}
.heart img{
position:absolute;
margin:50px;
width: 100px;
height: 100px;
border: 0
}
.heart p{
position:absolute;
width:200px;
padding: 0;
text-align: center;
margin-top: 8px;
font-weight: 700;
}
.block {
height: 100px;
}
.target {
height: 10px;
background: #999999;
}
</style>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/code.js"></script>
<script type="text/javascript">
$.extend($.easing, {
/**
* Heart Beat
*
* @param x
* @param t current time
* @param b begining
* @param c change in value
* @param d duration
*
* @link http://habrahabr.ru/blogs/mootools/43379/
*/
heart: function (x, t, b, c, d) {
if (x < 0.3) return Math.pow(x, 4) * 49.4;
if (x < 0.4) return 9 * x - 2.3;
if (x < 0.5) return -13 * x + 6.5;
if (x < 0.6) return 4 * x - 2;
if (x < 0.7) return 0.4;
if (x < 0.75) return 4 * x - 2.4;
if (x < 0.8) return -4 * x + 3.6;
if (x >= 0.8) return 1 - Math.sin(Math.acos(x));
},
heartIn: function (x, t, b, c, d) {
return $.easing.heart(x, t, b, c, d);
},
heartOut: function (x, t, b, c, d) {
return c - $.easing.heart(1 - x, t, b, c, d) + b;
},
heartInOut: function (x, t, b, c, d) {
if (t < d/2) return $.easing.heartIn(x, t, b, c, d);
return $.easing.heartOut(x, t, b, c, d);
}
});
$(function(){
$('.heart').click(function(){
var easing = $(this).data('easing');
$(this).find('img')
.animate({width:"-=100px",height:"-=100px",left:"+=50px",top:"+=50px"},3000, easing)
.animate({width:"+=100px",height:"+=100px",left:"-=50px",top:"-=50px"},200);
});
});
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu>
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
<hr/>
<pre><code>$(<span>'.target'</span>).animate({height:<span>'+=100px'</span>}, 2000, <span>'heartIn'</span>)</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'.target'</span>).animate({height:<span>'+=100px'</span>}, 2000, <span>'heartOut'</span>)</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'.target'</span>).animate({height:<span>'+=100px'</span>}, 2000, <span>'heartInOut'</span>)</code></pre>
<button type="button" class="code">Run Code</button>
</menu>
<header>
<h1>Пример расширять объект easing</h1>
<h2>Пробуем, играемся, и мотаем на ус (покликайте по сердцам)</h2>
</header>
<article>
<div class="block">
<div class="target"></div>
</div>
<div class="heart" data-easing="heartIn">
<p>heartIn</p>
<img src="images/heart.png" alt="Heart" />
</div>
<div class="heart" data-easing="heartOut">
<p>heartOut</p>
<img src="images/heart.png" alt="Heart" />
</div>
<div class="heart" data-easing="heartInOut">
<p>heartInOut</p>
<img src="images/heart.png" alt="Heart" />
</div>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
Когда я рассказывал о Sizzle я решил вас не грузить возможностями по расширению библиотеки, но вот время настало… В Sizzle можно расширять много чего:
Но браться мы будем лишь за расширение псевдо-селекторов, наподобие:
$("div:animated"); // поиск анимированных элементов
$("div:hidden"); // поиск скрытых элементов div
$("div:visible"); // поиск видимых элементов div
Почему я привел только эти фильтры? Всё просто — только они не входят в Sizzle, и относятся лишь к jQuery, именно такие плагины мы будем тренироваться разрабатывать. Начнем с кода фильтра ":hidden":
// пример для расширения Sizzle внутри jQuery
// для расширения самого Sizzle нужен чуть-чуть другой код
jQuery.expr.filters.hidden = function( elem ) {
// проверяем ширину и высоту каждого элемента в выборке
return elem.offsetWidth === 0 || elem.offsetHeight === 0;
};
Выглядит данный код несложно, но, пожалуй, я таки дам каркас для нового фильтра и добавлю чуть-чуть пояснений:
$.extend($.expr[':'], {
/**
* @param element DOM элемент
* @param i порядковый номер элемента
* @param match объект матчинга регулярного выражения
* @param elements массив всех найденных DOM элементов
*/
test: function(element, i, match, elements) {
/* тут будет наш код, и будет решать кто виноват */
return true || false; // выносим вердикт
}
})
Ну теперь попробуем решить следующую задачку:
Необходимо выделить ссылки в тексте в зависимости от её типа: внешняя, внутренняя или якорь
Для решения лучше всего подошли бы фильтры для селекторов следующего вида:
$("a:internal");
$("a:anchor");
$("a:external");
Поскольку "из коробки" данный функционал не доступен, мы напишем его сами, для этого нам понадобится не так уж и много :
$.extend($.expr[':'], {
// определения внешней ссылки
// нам понадобится лишь DOM Element
external: function(element) {
// а у нас ссылка?
if (element.tagName.toUpperCase() != 'A') return false;
// есть ли атрибут href
if (element.getAttribute('href')) {
var href = element.getAttribute('href');
// отсекаем ненужное
if ((href.indexOf('/') === 0) // внутренняя ссылка
|| (href.indexOf('#') === 0) // якорь
// наш домен по http:// или https://
|| (href.indexOf(window.location.hostname) === 7)
|| (href.indexOf(window.location.hostname) === 8)
) {
return false; // мимо
} else {
return true; // да, мы нашли внешние ссылки
}
} else {
return false;
}
}
})
Пример лишь для последнего ":external", рабочий код:
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример создания фильтра для Sizzle</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/code.js"></script>
<script>
(function($, window, undefined){
$.extend($.expr[':'], {
/**
* Get internal elements
*
* @param element DOM Element
* @param i Integer Index
* @param match Object All matched data
* @param elements Array of Dom Elements
*/
internal: function(element, i, match, elements) {
// internal links
if (element.tagName.toUpperCase() != 'A') return false;
if (element.getAttribute('href')) {
if ((element.getAttribute('href').indexOf('/') === 0)
|| (element.getAttribute('href').indexOf(window.location.hostname) === 7) // http://...
|| (element.getAttribute('href').indexOf(window.location.hostname) === 8) // https://...
) {
return true;
} else {
return false;
}
} else {
return false;
}
},
anchor: function(element) {
// anchor only
if (element.tagName.toUpperCase() != 'A') return false;
if (element.getAttribute('href')) {
return (element.getAttribute('href').indexOf('#') === 0);
} else {
return false;
}
},
external: function(element) {
// external link
if (element.tagName.toUpperCase() != 'A') return false;
if (element.getAttribute('href')) {
if ((element.getAttribute('href').indexOf('/') === 0)
|| (element.getAttribute('href').indexOf('#') === 0) // #anchor
|| (element.getAttribute('href').indexOf(window.location.hostname) === 7) // http://...
|| (element.getAttribute('href').indexOf(window.location.hostname) === 8) // https://...
) {
return false;
} else {
return true;
}
} else {
return false;
}
}
})
})(jQuery, window);
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu>
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back</a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
<hr/>
<pre><code>$(<span>'a:internal'</span>).css({
color:<span>'#ff5555'</span>,
fontSize:<span>'24px'</span>
});</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'a:anchor'</span>).css({
color:<span>'#33aa33'</span>,
fontSize:<span>'24px'</span>
});</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'a:external'</span>).css({
color:<span>'#5555ff'</span>,
fontSize:<span>'24px'</span>
});</code></pre>
<button type="button" class="code">Run Code</button>
</menu>
<header>
<h1>Пример создания фильтра для Sizzle</h1>
<h2>Пробуем найти элементы по своему фильтру</h2>
</header>
<article>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut lacinia quam nec enim scelerisque porta.
In ut lorem ipsum. <a href="#">Proin iaculis viverra rutrum</a>. Maecenas quis ante enim. Vestibulum ante ipsum primis
in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum luctus tristique feugiat. Morbi
dictum est dolor, at condimentum nisl. Ut id nunc augue, at luctus enim. Phasellus urna nunc, aliquam
sit amet rutrum ac, imperdiet in nunc. Cras mattis massa et est sodales ac auctor mi sagittis. Fusce
elementum ultrices nunc, eu scelerisque massa sodales quis. Aliquam bibendum accumsan nibh ut blandit.
Vestibulum suscipit leo vitae magna tincidunt pharetra. Sed consequat, ligula non pretium ultrices,
ligula enim venenatis urna, in scelerisque urna turpis vel neque. Praesent nibh urna, eleifend et
fringilla eu, pellentesque sit amet nisl.
</p>
<p>
<a href="http://google.com">Praesent venenatis dictum ante</a>, et viverra diam ultrices et. Maecenas egestas augue eget nibh
consequat tempus. Quisque facilisis ipsum non mi molestie faucibus. Integer sapien est, mattis
eu suscipit nec, auctor id dolor. In hac habitasse platea dictumst. Duis adipiscing tristique
ipsum, ut tempus eros porta ut. Nulla enim quam, auctor ut venenatis at, pharetra eget enim.
Nam suscipit quam eu erat adipiscing aliquam. Etiam aliquet sagittis viverra. <a href="http://anton.shevchuk.name/jquery/">Ut ultricies</a>,
neque nec bibendum commodo, sem arcu lobortis nunc, ut luctus tellus erat at sem.
</p>
<p>
Aliquam erat volutpat. <a href="/index.html">Nunc vel augue sagittis lacus aliquet consequat</a>. Integer eros risus,
posuere non volutpat a, mattis et elit. Curabitur congue enim in purus sagittis eu ultrices sem
sollicitudin. Morbi sit amet nibh sapien. Nulla facilisi. Integer neque purus, commodo eu tempor eu,
interdum sit amet quam. <a href="http://hohli.com/">Vestibulum mollis ullamcorper ipsum sit amet imperdiet</a>. Nullam lorem nunc,
scelerisque ac volutpat ut, elementum ut risus. Integer placerat turpis purus. Vestibulum quis turpis
ut justo sodales vulputate. Duis lectus tellus, gravida non commodo eu, dictum a tellus.
Praesent a nibh vel nisl sodales tincidunt at ac leo.
</p>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
ВСЕГДА используйте фильтр вместе с HTML тэгом который ищите:
$("tag:filter")
Это один из пунктов оптимизации работы с фильтрами jQuery, иначе ваш фильтр будет обрабатывать все DOM элементы на странице, а это может очень сильно сказаться на производительности. Если же у вас несколько тегов, то пишите уж лучше так — "$("tag1:filter, tag2:filter, tag3:filter")", или ещё лучше через вызов метода "filter()".
— "Sizzle Documentation" — скудненькая официальная документация [https://github.com/jquery/sizzle/wiki/Sizzle-Documentation]
Для начала вспомним, для чего нам нужны плагины? Мой ответ — создание повторно используемого кода, и да — с удобным интерфейсом. Давайте напишем такой код, вот простая задачка: "По клику на параграф, текст должен измениться на красный"
Дабы не забывать истоков — начнем с реализации на нативном JavaScript'е:
var loader = function () {
// находим все параграфы
var para = document.getElementsByTagName('P');
// перебираем все, и вешаем обработчик
for (var i=0,size=para.length;i<size;i++) {
// обработчик
para[i].onclick = function() {
this.style.color = "#FF0000";
}
}
}
// естественно, весь код должен работать после загрузки всей страницы
document.addEventListener("DOMContentLoaded", loader, false);
Данный код не является кроссбраузерным, и написан с целью лишний раз подчеркнуть удобство использования библиотеки ;)
Теперь можно этот код упростить, подключаем jQuery и получаем следующий вариант:
$(function(){
$('p').click(function(){
$(this).css('color', '#ff0000');
})
});
С поставленной задачей мы справились, но где тут повторное использование
кода? Или если нам надо не в красный, а в зеленый перекрасить? Вот тут
начинается самое интересное, чтобы написать простой плагин достаточно
расширить объект $.fn:
$.fn.mySimplePlugin = function() {
$(this).click(function(){
$(this).css('color', '#ff0000');
})
}
Если же писать более грамотно, то нам необходимо ограничить переменную $ только нашим плагином, а так же возвращать "this", чтобы можно было использовать цепочки вызовов (т.н. "chaining"), делается это следующим образом:
(function($) {
$.fn.mySimplePlugin = function(){
// код плагина
return this;
};
})(jQuery);
Внесу небольшое пояснение о происходящей тут "магии", код
"(function($){…})(jQuery)" создает анонимную функцию, и тут же
вызывает ее, передавая в качестве параметра объект jQuery, таким
образом внутри анонимной функции мы можем использовать алиас $
не боясь за конфликты с другими библиотеками — так как теперь $
находится лишь в области видимости нашей функции, и мы имеем
полный контроль над ней. Если у вас возникло ощущение дежавю – то
всё верно, я об этом уже рассказывал
Добавим опцию по выбору цвета и получим рабочий плагин:
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример плагина с лобальными настройками</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/code.js"></script>
<script type="text/javascript">
(function($) {
// значени по умолчанию
var defaults = {
color:'green'
};
// актуальные настройки, глобальные
var options;
$.fn.mySimplePlugin = function(params){
// при многократном вызове функции настройки будут сохранятся, и замещаться при необходимости
options = $.extend({}, defaults, options, params);
$(this).click(function(){
$(this).css('color', options.color);
});
return this;
};
})(jQuery);
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu>
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
<hr/>
<pre><code>$(<span>'p:eq(0)'</span>).mySimplePlugin()</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'p:eq(1)'</span>).mySimplePlugin({'color':'red'})</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'p:eq(2)'</span>).mySimplePlugin({'color':'blue'})</code></pre>
<button type="button" class="code">Run Code</button>
</menu>
<header>
<h1>Пример плагина с глобальными настройками</h1>
<h2>Запустите плагин с новыми настройками и проклацайте все параграфы</h2>
</header>
<article>
<h2>Article Title</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
</article>
<article>
<h2>Article Title</h2>
<p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
libero. Fusce et ultrices lectus.</p>
</article>
<article>
<h2>Article</h2>
<p>Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
(function($) {
// значение по умолчанию - ЗЕЛЁНЫЙ
var defaults = { color:'green' };
// актуальные настройки, глобальные
var options;
$.fn.mySimplePlugin = function(params){
// при многократном вызове настройки будут сохранятся
// и замещаться при необходимости
options = $.extend({}, defaults, options, params);
$(this).click(function(){
$(this).css('color', options.color);
});
return this;
};
})(jQuery);
Вызов:
// первый вызов
$('p:first,p:last').mySimplePlugin();
// второй вызов
$('p:eq(1)').mySimplePlugin({ color: 'red' });
В результате работы данного плагина, каждый клик будет изменять цвет параграфа на красный, т.к. мы используем глобальную переменную для хранения настроек, то второй вызов плагина изменят значение для всех элементов.
Можно внести небольшие изменения, и разделить настройки для каждого вызова:
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример плагина с индивидуальными настройками</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/code.js"></script>
<script type="text/javascript">
(function($) {
// значени по умолчанию
var defaults = {
color:'green'
};
$.fn.mySimplePlugin = function(params){
// актуальные настройки, будут индивидуальными при каждом запуске
var options = $.extend({}, defaults, options, params);
$(this).click(function(){
$(this).css('color', options.color);
});
return this;
};
})(jQuery);
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu>
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
<hr/>
<pre><code>$(<span>'p:eq(0)'</span>).mySimplePlugin()</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'p:eq(1)'</span>).mySimplePlugin({'color':'red'})</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'p:eq(2)'</span>).mySimplePlugin({'color':'blue'})</code></pre>
<button type="button" class="code">Run Code</button>
</menu>
<header>
<h1>Пример плагина с индивидуальными настройками</h1>
<h2>Разница лишь в одном объявлении var</h2>
</header>
<article>
<h2>Article Title</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
</article>
<article>
<h2>Article Title</h2>
<p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
libero. Fusce et ultrices lectus.</p>
</article>
<article>
<h2>Article</h2>
<p>Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
// актуальные настройки, будут индивидуальными при каждом запуске
var options = $.extend({}, defaults, params);
А разница то в одном "var". Мне даже сложно себе представить как много часов убито в поисках потерянного "var" в JavaScript'е, будьте внимательны
Тут все просто, достаточно запомнить — "this" содержит jQuery объект с коллекцией всех элементов, т.е.:
$.fn.mySimplePlugin = function(){
console.log(this); // это jQuery объект
console.log(this.length); // число элементов в выборке
};
Если мы хотим обрабатывать каждый элемент то соорудим следующую конструкцию внутри нашего плагина:
// необходимо обработать каждый элемент в коллекции
return this.each(function(){
$(this).click(function(){
$(this).css('color', options.color);
});
});
// предыдущий вариант немного избыточен,
// т.к. внутри функции click и так есть перебор элементов
return this.click(function(){
$(this).css('color', options.color);
});
Опять же напомню, если ваш плагин не должен что-то возвращать по вашей задумке — возвращайте "this" — цепочки вызовов в jQuery это часть магии, не стоит её разрушать. Методы "each()" и "click()" возвращают объект jQuery.
Так, у нас написан крутой плагин, надо бы ему еще докрутить функционала, пусть цвет регулируется несколькими кнопками на сайте. Для этого нам понадобится некий метод "color", который и будет в ответе за всё. Сейчас приведу пример кода готового плагина — будем курить вместе (обращайте внимание на комментарии):
// настройки со значением по умолчанию
var defaults = { color:'green' };
// наши будущие публичные методы
var methods = {
// инициализация плагина
init: function(params) {
// настройки, будут индивидуальными при каждом запуске
var options = $.extend({}, defaults, params);
// инициализируем лишь единожды
if (!this.data('mySimplePlugin')) {
// закинем настройки в реестр data
this.data('mySimplePlugin', options);
// добавим событий
this.bind('click.mySimplePlugin', function(){
$(this).css('color', options.color);
});
}
return this;
},
// изменяем цвет в реестре
color: function(color) {
var options = $(this).data('mySimplePlugin');
options.color = color;
$(this).data('mySimplePlugin', options);
},
// сброс цвета элементов
reset: function() {
$(this).css('color', 'black');
}
};
$.fn.mySimplePlugin = function(method){
// немного магии
if ( methods[method] ) {
// если запрашиваемый метод существует, мы его вызываем
// все параметры, кроме имени метода прийдут в метод
// this так же перекочует в метод
return methods[ method ].apply( this, Array.prototype.slice.call(
arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
// если первым параметром идет объект, либо совсем пусто
// выполняем метод init
return methods.init.apply( this, arguments );
} else {
// если ничего не получилось
$.error('Метод "' + method + '" в плагине не найден');
}
};
Теперь еще небольшой пример использование данных методов:
// вызов без параметров - будет вызван init
$('p').mySimplePlugin();
// вызов метода color и передача цвета в качестве параметров
$('p').mySimplePlugin('color', '#FFFF00');
// вызов метода reset
$('p').mySimplePlugin('reset');
Для понимания данного кусочка кода, вы должны разобраться лишь с переменной "arguments", и с методом "apply()". Тут им целые статьи посвятили, дерзайте:
Если ваш плагин вешает какой-либо обработчик, то лучше всего (читай всегда) данный обработчик повесить в своём собственном namespace:
return this.bind("click.mySimplePlugin", function(){
$(this).css('color', options.color);
});
Данный финт позволит в любой момент убрать все ваши обработчики, или вызвать только ваш, что очень удобно:
// вызовем лишь наш обработчик
$('p').trigger("click.mySimplePlugin");
// убираем все наши обработчики
$('p').unbind(".mySimplePlugin");
Дежавю? Ок!
На этом об обычных плагинах всё, хотя дам ещё чуток информации к размышлению, но на английском:
Если по какой-то причине вы еще не знакомы с "data()" — то советую прочитать документацию и усвоить незамедлительно. Если же в двух словах — это реестр данных, и все данные привязанные к какому-либо элементу лучше хранить в нём, это же правило касается и плагинов. Если вам надо сохранить состояние плагина — используйте "data()", если необходим кеш — используйте "data()", если вам необходимо сохранить … ну думаю понятно. Приведу еще примерчик связанный с инициализацией:
function() { // функция init
var init = $(this).data('mySimplePlugin');
if (init) {
return this;
} else {
$(this).data('mySimplePlugin', true);
return this.bind('click.mySimplePlugin', function(){
$(this).css('color', options.color);
});
}
}
По стечению обстоятельств, в HTML5 появились data-атрибуты, и для доступа к ним jQuery использует тот же метод "data()", но вот дела, "jQuery.data()" – не манипулирует атрибутами HTML, а работает со своим реестром, и лишь при отсутствии там данных пытается заполучить атрибут "data-*", не попадитесь:
<div id="my" data-foo="bar"></div>
$("#my").data("foo"); // >>> bar
$("#my").attr("data-foo"); // >>> bar
$("#my").data("foo", "xyz");
$("#my").data("foo"); // >>> xyz
$("#my").attr("data-foo"); // >>> bar
$("#my").attr("data-foo", "def");
$("#my").data("foo"); // >>> xyz
$("#my").attr("data-foo"); // >>> def
<div id="my" data-foo="def"></div>
Информация в данном разделе актуальна для jQuery версии 1.8 и выше, если вас заинтересуют возможности расширения для более старых версий, то читайте мою статью "Пишем плагины анимации"
Для начала затравка – метод "animate()" манипулирует объектом "jQuery.Animation", который предусматривает следующие точки для расширения функционала:
Начну рассказ с "jQuery.Tween.propHooks", т.к. уже есть плагины, в код которых можно заглянуть :) Для пущей наглядности я возьму достаточно тривиальную задачу – заставим плавно изменить цвет шрифта для заданного набора элементов:
$('p').animate({color:'#ff0000'});
Приведенный выше код не даст никакого эффекта, т.к. свойство "color" библиотека из коробки не анимирует, но это можно исправить – надо лишь прокачать "jQuery.Tween.propHooks":
$.Tween.propHooks.color = {
get: function(tween) {
return tween.elem.style.color;
}
set: function(tween) {
tween.easing; // текущий easing
tween.elem; // испытуемый элемент
tween.options; // настройки анимация
tween.pos; // текущий прогресс
tween.prop; // свойство которое изменяем
tween.start; // начальные значения
tween.now; // текущее значение
tween.end; // желаемое результирующие значения
tween.unit; // еденицы измерения
}
}
Этот код ещё не работает, это наши кирпичики, с их помощью будем строить нашу анимацию. Перед работой стоит заглянуть внутрь каждого из приведенных свойств:
console.log(tween);
>>>
easing: "swing"
elem: HTMLParagraphElement
end: "#ff0000"
now: "NaNrgb(0, 0, 0)"
options: Object
complete: function (){}
duration: 1000
old: false
queue: "fx"
specialEasing: Object
pos: 1
prop: "color"
start: "rgb(0, 0, 0)"
unit: "px"
В консоле у нас будет очень много данных, т.к. приведённый метод вызывается N кол-во раз, в зависимости от продолжительности анимации, при этом "tween.pos" постепенно наращивает своё значение с 0 до 1. По умолчанию, наращивание происходит линейно, если надо как-то иначе — то стоит посмотретьли дочитать раздел до конца (об этом я уже упоминал в лекции Анимация)
Даже при таком раскладе мы уже можем изменять выбранный элемент (путём манипуляций над "tween.elem"), но есть более удобный способ – можно установить свойство "run" объекта "tween":
$.Tween.propHooks.color = {
set: function(tween) {
// тут будет инициализация
tween.run = function(progress) {
// тут код отвечающий за измение свойств элемента
}
}
}
Получившийся код будет работать следующим образом:
Теперь, возвращаясь к изначальной задачи по изменению цвета можно наворотить следующий код:
$.Tween.propHooks.color = {
set: function(tween) {
// приводим начальное и конечное значения к единому формату
// #FF0000 == [255,0,0]
tween.start = parseColor(tween.start);
tween.end = parseColor(tween.end);
tween.run = function(progress) {
tween.elem.style['color'] =
// вычисляем промежуточное значение
buildColor(tween.start, tween.end, progress);
}
}
}
Код функций "parseColor()" и "buildColor()" вы найдёте в листинге на странице color.html
Результатом станет плавное перетекание исходного цвета к красному (#F00 == #FF0000 == 255,0,0), вживую можно посмотреть на странице со следующим кодом
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример расширения анимации</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript">
/**
* @author Anton Shevchuk (AntonShevchuk@gmail.com)
* @uri http://anton.shevchuk.name
*/
(function($) {
/**
* parse HEX color to DEC
* e.g. #f00 => [255,0,0]
* #ff00ff => [255,0,255]
* @param {Array} color
*/
function parseColor(color) {
if (color.length != 4
color.length != 7) {
return [0,0,0];
}
color = color.substr(1);
if (color.length == 3) {
color = color[0]+color[0]+
color[1]+color[1]+
color[2]+color[2];
}
var R = parseInt(color.substr(0,2),16);
var G = parseInt(color.substr(2,2),16);
var B = parseInt(color.substr(4,2),16);
return [R,G,B];
};
/**
* Build color for apply as style
*/
function buildColor(start, end, progress) {
// нехитрое вычисление
var R = Math.round(((end[0] - start[0]) * progress) + start[0]);
var G = Math.round(((end[1] - start[1]) * progress) + start[1]);
var B = Math.round(((end[2] - start[2]) * progress) + start[2]);
return 'rgb('+R+','+G+','+B+')';
}
$.Tween.propHooks.color = {
set: function(tween) {
// console.log(tween);
// испытуемый элемент
tween.elem;
// текущий easing
tween.easing
// настройки анимация
tween.options;
// текущий прогресс
tween.pos;
// свойство которое изменяем
tween.prop;
// начальные значения
tween.start = parseColor(tween.start);
// текущее значение
tween.now;
// результирующие значения
tween.end = parseColor(tween.end);
// еденицы измерения
tween.unit;
tween.run = function(progress) {
// непосредственно измение свойств элемента
tween.elem.style['color'] = buildColor(tween.start, tween.end, progress);
}
}
}
})(jQuery);
$(function(){
$('p:eq(0)').animate({color:'#ff0000'},1000);
$('p:eq(1)').animate({color:'#ff0000'},3000);
$('p:eq(2)').animate({color:'#ff0000'},5000);
});
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu>
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
</menu>
<header>
<h1>Пример расширения анимации</h1>
<h2>Данный пример работает лишь с jQuery 1.8+</h2>
</header>
<article>
<h2>Article Title</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
</article>
<article>
<h2>Article Title</h2>
<p>Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
libero. Fusce et ultrices lectus.</p>
</article>
<article>
<h2>Article</h2>
<p>Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
В плагине jQuery Color для решения поставленной задачи использовали jQuery.cssHooks, но мы же не ищем лёгких путей.
Ещё хотел было рассказать про префильтры анимации, но – документации нет, а как использовать "в жизни" – я не догадался, но чуть-чуть информации таки накопал (код можно найти в функции "Animation"):
jQuery.Animation.prefilter(function(element, props, opts) {
// deffered объект animate
element; // искомый элемент
props; // настройки анимации из animate()
opts; // опции анимации
// отключаем анимацию при попытке анимировать высоту элемента
if (props['height'] != undefined) {
return this;
}
});
Пример:
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример расширения animate через префильтры</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/code.js"></script>
<script>
// jQuery.Animation.preFilter( function( element, props, opts ) {})
jQuery.Animation.prefilter(function(element, props, opts) {
//console.log(this, element, props, opts);
// disable "height" animation
if (props['height'] != undefined) {
return this;
}
});
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu label="Try...">
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
<hr/>
<pre><code contenteditable="true">$(<span>'img'</span>).animate({
<span>'width'</span>:<span>'+=20px'</span>
}, 2000)</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code contenteditable="true"><em>// disabled</em>
$(<span>'img'</span>).animate({
<span>'height'</span>:<span>'+=20px'</span>
}, 2000)</code></pre>
<button type="button" class="code">Run Code</button>
</menu>
<header>
<h1>Анимация</h1>
<h2>Пример расширения через префильтры</h2>
</header>
<article id="stick" class="box">
<h2>Article</h2>
<p>
<img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200"/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
Про "jQuery.Animation.tweener" так же много не расскажешь, но пример получилось сделать чуток интересней – приведённый код позволяет анимировать ширину и высоту объекта по заданной диагонали:
Осторожно, для понимания происходящего потребуются знания геометрии за 8-ой класс
// создаём поддержку нового свойства для анимации – diagonal
jQuery.Animation.tweener( "diagonal", function( property, value ) {
// создаём tween объект
var tween = this.createTween( property, value );
// промежуточные вычисления и данные
var a = jQuery.css(tween.elem, 'width', true );
var b = jQuery.css(tween.elem, 'height', true );
var c = Math.sqrt(a*a + b*b), sinA = a/c, sinB = b/c;
tween.start = c;
tween.end = value;
tween.run = function(progress) {
// вычисление искомого значения – новое значение гипотенузы
var hyp = this.start + ((this.end - this.start) * progress);
// непосредственно измение свойств элемента
tween.elem.style.width = sinA*hyp + tween.unit; // ширина
tween.elem.style.height = sinB*hyp + tween.unit; // высота
};
return tween;
});
Пример работы:
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример расширения animate через tweener</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/code.js"></script>
<script>
// jQuery.Animation.tweener( propsList, function( property, value ) {})
jQuery.Animation.tweener( "diagonal", function( property, value ) {
var tween = this.createTween( property, value );
var target = tween.cur();
var elem = tween.elem;
/*
A
|\
b | \ c
| \
|___\
C a B
*/
var a = jQuery.css( elem, 'width', true );
var b = jQuery.css( elem, 'height', true );
var c = Math.sqrt(a*a + b*b);
var sinA = a/c;
var sinB = b/c;
appendOut('Hypotenuse (start): '+c+'\n');
tween.start = c;
tween.end = value;
tween.run = function(progress) {
// вычисление искомого значения
var newc = this.start + ((this.end - this.start) * progress);
var width = sinA*newc;
var height = sinB*newc;
// непосредственно измение свойств элемента
if (progress == 1) {
width = Math.ceil(width);
height = Math.ceil(height);
appendOut('Hypotenuse (finish): '+Math.sqrt(width*width + height*height)+'\n');
}
tween.elem.style.width = width + tween.unit;
tween.elem.style.height = height + tween.unit;
};
return tween;
});
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu label="Try...">
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
<hr/>
<pre><code contenteditable="true">$(<span>'img'</span>).animate({
<span>'diagonal'</span>:400
}, 2000)</code></pre>
<button type="button" class="code">Run Code</button>
</menu>
<div id="output">
<h3>Output</h3>
<pre></pre>
</div>
<header>
<h1><a href="index.html">Анимация</a></h1>
<h2>Пример расширения анимации через tweener</h2>
</header>
<article id="stick" class="box">
<h2>Article</h2>
<p>
<img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200" />
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
Теперь опять обратимся к easing’у – приведу пример произвольной функции, которой будет следовать анимация. Дабы особо не фантазировать – я взял пример из статьи на вездесущем хабре o анимации в MooTools фреймворке [http://habrahabr.ru/post/43379/] – наглядный пример с сердцебиением, которое описывается следующими функциями:
В расширении функционала easing нет ничего военного:
$.extend($.easing, {
/**
* Heart Beat
*
* @param x progress
* @param t current time
* @param b = 0
* @param c = 1
* @param d duration
* @link http://habrahabr.ru/blogs/mootools/43379/
*/
heart:function(x, t, b, c, d) {
if (x < 0.3) return Math.pow(x, 4) * 49.4;
if (x < 0.4) return 9 * x - 2.3;
if (x < 0.5) return -13 * x + 6.5;
if (x < 0.6) return 4 * x - 2;
if (x < 0.7) return 0.4;
if (x < 0.75) return 4 * x - 2.4;
if (x < 0.8) return -4 * x + 3.6;
if (x >= 0.8) return 1 - Math.sin(Math.acos(x));
}
});
Чуть-чуть пояснений, конструкция "$.extend({}, {})" "смешивает" объекты:
$.extend({name:"Anton"}, {location:"Kharkiv"});
>>>
{
name:"Anton",
location:"Kharkiv"
}
$.extend({name:"Anton", location:"Kharkiv"}, {location:"Kyiv"});
>>>
{
name:"Anton",
location:"Kyiv"
}
Таким образом мы "вмешиваем" новый метод к существующему объекту "$.easing"; согласно коду, наш метод принимает в качестве параметров следующие значения:
Результат конечно интересен, но его можно ещё чуть-чуть расширить дополнительными функциями (развернем и скомбинируем):
heartIn: function (x, t, b, c, d) {
return $.easing.heart(x, t, b, c, d);
},
heartOut: function (x, t, b, c, d) {
return c - $.easing.heart(1 - x, t, b, c, d) + b;
},
heartInOut: function (x, t, b, c, d) {
if (t < d/2) return $.easing.heartIn(x, t, b, c, d);
return $.easing.heartOut(x, t, b, c, d);
}
Получим следующие производные функции:
Работать с данным творением надо следующим образом:
$("#my").animate({height:"+200px"}, 2000, "heartIn"); // вот оно
Пример работы данной функции можно пощупать на примере
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример расширения объекта easing</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<style type="text/css">
.heart{
width:200px;
height:200px;
background:#fff;
margin:0 auto;
overflow:hidden;
position: relative;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
margin-bottom: 4px;;
}
.heart img{
position:absolute;
margin:50px;
width: 100px;
height: 100px;
border: 0
}
.heart p{
position:absolute;
width:200px;
padding: 0;
text-align: center;
margin-top: 8px;
font-weight: 700;
}
.block {
height: 100px;
}
.target {
height: 10px;
background: #999999;
}
</style>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/code.js"></script>
<script type="text/javascript">
$.extend($.easing, {
/**
* Heart Beat
*
* @param x
* @param t current time
* @param b begining
* @param c change in value
* @param d duration
*
* @link http://habrahabr.ru/blogs/mootools/43379/
*/
heart: function (x, t, b, c, d) {
if (x < 0.3) return Math.pow(x, 4) * 49.4;
if (x < 0.4) return 9 * x - 2.3;
if (x < 0.5) return -13 * x + 6.5;
if (x < 0.6) return 4 * x - 2;
if (x < 0.7) return 0.4;
if (x < 0.75) return 4 * x - 2.4;
if (x < 0.8) return -4 * x + 3.6;
if (x >= 0.8) return 1 - Math.sin(Math.acos(x));
},
heartIn: function (x, t, b, c, d) {
return $.easing.heart(x, t, b, c, d);
},
heartOut: function (x, t, b, c, d) {
return c - $.easing.heart(1 - x, t, b, c, d) + b;
},
heartInOut: function (x, t, b, c, d) {
if (t < d/2) return $.easing.heartIn(x, t, b, c, d);
return $.easing.heartOut(x, t, b, c, d);
}
});
$(function(){
$('.heart').click(function(){
var easing = $(this).data('easing');
$(this).find('img')
.animate({width:"-=100px",height:"-=100px",left:"+=50px",top:"+=50px"},3000, easing)
.animate({width:"+=100px",height:"+=100px",left:"-=50px",top:"-=50px"},200);
});
});
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu>
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
<hr/>
<pre><code>$(<span>'.target'</span>).animate({height:<span>'+=100px'</span>}, 2000, <span>'heartIn'</span>)</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'.target'</span>).animate({height:<span>'+=100px'</span>}, 2000, <span>'heartOut'</span>)</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'.target'</span>).animate({height:<span>'+=100px'</span>}, 2000, <span>'heartInOut'</span>)</code></pre>
<button type="button" class="code">Run Code</button>
</menu>
<header>
<h1>Пример расширять объект easing</h1>
<h2>Пробуем, играемся, и мотаем на ус (покликайте по сердцам)</h2>
</header>
<article>
<div class="block">
<div class="target"></div>
</div>
<div class="heart" data-easing="heartIn">
<p>heartIn</p>
<img src="images/heart.png" alt="Heart" />
</div>
<div class="heart" data-easing="heartOut">
<p>heartOut</p>
<img src="images/heart.png" alt="Heart" />
</div>
<div class="heart" data-easing="heartInOut">
<p>heartInOut</p>
<img src="images/heart.png" alt="Heart" />
</div>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
Когда я рассказывал о Sizzle я решил вас не грузить возможностями по расширению библиотеки, но вот время настало… В Sizzle можно расширять много чего:
Но браться мы будем лишь за расширение псевдо-селекторов, наподобие:
$("div:animated"); // поиск анимированных элементов
$("div:hidden"); // поиск скрытых элементов div
$("div:visible"); // поиск видимых элементов div
Почему я привел только эти фильтры? Всё просто — только они не входят в Sizzle, и относятся лишь к jQuery, именно такие плагины мы будем тренироваться разрабатывать. Начнем с кода фильтра ":hidden":
// пример для расширения Sizzle внутри jQuery
// для расширения самого Sizzle нужен чуть-чуть другой код
jQuery.expr.filters.hidden = function( elem ) {
// проверяем ширину и высоту каждого элемента в выборке
return elem.offsetWidth === 0 || elem.offsetHeight === 0;
};
Выглядит данный код несложно, но, пожалуй, я таки дам каркас для нового фильтра и добавлю чуть-чуть пояснений:
$.extend($.expr[':'], {
/**
* @param element DOM элемент
* @param i порядковый номер элемента
* @param match объект матчинга регулярного выражения
* @param elements массив всех найденных DOM элементов
*/
test: function(element, i, match, elements) {
/* тут будет наш код, и будет решать кто виноват */
return true || false; // выносим вердикт
}
})
Ну теперь попробуем решить следующую задачку:
Необходимо выделить ссылки в тексте в зависимости от её типа: внешняя, внутренняя или якорь
Для решения лучше всего подошли бы фильтры для селекторов следующего вида:
$("a:internal");
$("a:anchor");
$("a:external");
Поскольку "из коробки" данный функционал не доступен, мы напишем его сами, для этого нам понадобится не так уж и много :
$.extend($.expr[':'], {
// определения внешней ссылки
// нам понадобится лишь DOM Element
external: function(element) {
// а у нас ссылка?
if (element.tagName.toUpperCase() != 'A') return false;
// есть ли атрибут href
if (element.getAttribute('href')) {
var href = element.getAttribute('href');
// отсекаем ненужное
if ((href.indexOf('/') === 0) // внутренняя ссылка
|| (href.indexOf('#') === 0) // якорь
// наш домен по http:// или https://
|| (href.indexOf(window.location.hostname) === 7)
|| (href.indexOf(window.location.hostname) === 8)
) {
return false; // мимо
} else {
return true; // да, мы нашли внешние ссылки
}
} else {
return false;
}
}
})
Пример лишь для последнего ":external", рабочий код:
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Пример создания фильтра для Sizzle</title>
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
<link rel="stylesheet" href="css/styles.css"/>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/code.js"></script>
<script>
(function($, window, undefined){
$.extend($.expr[':'], {
/**
* Get internal elements
*
* @param element DOM Element
* @param i Integer Index
* @param match Object All matched data
* @param elements Array of Dom Elements
*/
internal: function(element, i, match, elements) {
// internal links
if (element.tagName.toUpperCase() != 'A') return false;
if (element.getAttribute('href')) {
if ((element.getAttribute('href').indexOf('/') === 0)
|| (element.getAttribute('href').indexOf(window.location.hostname) === 7) // http://...
|| (element.getAttribute('href').indexOf(window.location.hostname) === 8) // https://...
) {
return true;
} else {
return false;
}
} else {
return false;
}
},
anchor: function(element) {
// anchor only
if (element.tagName.toUpperCase() != 'A') return false;
if (element.getAttribute('href')) {
return (element.getAttribute('href').indexOf('#') === 0);
} else {
return false;
}
},
external: function(element) {
// external link
if (element.tagName.toUpperCase() != 'A') return false;
if (element.getAttribute('href')) {
if ((element.getAttribute('href').indexOf('/') === 0)
|| (element.getAttribute('href').indexOf('#') === 0) // #anchor
|| (element.getAttribute('href').indexOf(window.location.hostname) === 7) // http://...
|| (element.getAttribute('href').indexOf(window.location.hostname) === 8) // https://...
) {
return false;
} else {
return true;
}
} else {
return false;
}
}
})
})(jQuery, window);
</script>
</head>
<body>
<div id="content" class="wrapper box">
<menu>
<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back</a>
<a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
<hr/>
<pre><code>$(<span>'a:internal'</span>).css({
color:<span>'#ff5555'</span>,
fontSize:<span>'24px'</span>
});</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'a:anchor'</span>).css({
color:<span>'#33aa33'</span>,
fontSize:<span>'24px'</span>
});</code></pre>
<button type="button" class="code">Run Code</button>
<pre><code>$(<span>'a:external'</span>).css({
color:<span>'#5555ff'</span>,
fontSize:<span>'24px'</span>
});</code></pre>
<button type="button" class="code">Run Code</button>
</menu>
<header>
<h1>Пример создания фильтра для Sizzle</h1>
<h2>Пробуем найти элементы по своему фильтру</h2>
</header>
<article>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut lacinia quam nec enim scelerisque porta.
In ut lorem ipsum. <a href="#">Proin iaculis viverra rutrum</a>. Maecenas quis ante enim. Vestibulum ante ipsum primis
in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum luctus tristique feugiat. Morbi
dictum est dolor, at condimentum nisl. Ut id nunc augue, at luctus enim. Phasellus urna nunc, aliquam
sit amet rutrum ac, imperdiet in nunc. Cras mattis massa et est sodales ac auctor mi sagittis. Fusce
elementum ultrices nunc, eu scelerisque massa sodales quis. Aliquam bibendum accumsan nibh ut blandit.
Vestibulum suscipit leo vitae magna tincidunt pharetra. Sed consequat, ligula non pretium ultrices,
ligula enim venenatis urna, in scelerisque urna turpis vel neque. Praesent nibh urna, eleifend et
fringilla eu, pellentesque sit amet nisl.
</p>
<p>
<a href="http://google.com">Praesent venenatis dictum ante</a>, et viverra diam ultrices et. Maecenas egestas augue eget nibh
consequat tempus. Quisque facilisis ipsum non mi molestie faucibus. Integer sapien est, mattis
eu suscipit nec, auctor id dolor. In hac habitasse platea dictumst. Duis adipiscing tristique
ipsum, ut tempus eros porta ut. Nulla enim quam, auctor ut venenatis at, pharetra eget enim.
Nam suscipit quam eu erat adipiscing aliquam. Etiam aliquet sagittis viverra. <a href="http://anton.shevchuk.name/jquery/">Ut ultricies</a>,
neque nec bibendum commodo, sem arcu lobortis nunc, ut luctus tellus erat at sem.
</p>
<p>
Aliquam erat volutpat. <a href="/index.html">Nunc vel augue sagittis lacus aliquet consequat</a>. Integer eros risus,
posuere non volutpat a, mattis et elit. Curabitur congue enim in purus sagittis eu ultrices sem
sollicitudin. Morbi sit amet nibh sapien. Nulla facilisi. Integer neque purus, commodo eu tempor eu,
interdum sit amet quam. <a href="http://hohli.com/">Vestibulum mollis ullamcorper ipsum sit amet imperdiet</a>. Nullam lorem nunc,
scelerisque ac volutpat ut, elementum ut risus. Integer placerat turpis purus. Vestibulum quis turpis
ut justo sodales vulputate. Duis lectus tellus, gravida non commodo eu, dictum a tellus.
Praesent a nibh vel nisl sodales tincidunt at ac leo.
</p>
</article>
<footer>
©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
</footer>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1669896-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</div>
</body>
</html>
ВСЕГДА используйте фильтр вместе с HTML тэгом который ищите:
$("tag:filter")
Это один из пунктов оптимизации работы с фильтрами jQuery, иначе ваш фильтр будет обрабатывать все DOM элементы на странице, а это может очень сильно сказаться на производительности. Если же у вас несколько тегов, то пишите уж лучше так — "$("tag1:filter, tag2:filter, tag3:filter")", или ещё лучше через вызов метода "filter()".
— "Sizzle Documentation" — скудненькая официальная документация [https://github.com/jquery/sizzle/wiki/Sizzle-Documentation]
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.