Введение в jQuery

Пишем свой плагин

Разбить на страницы
Показывать лекцию целиком

jQuery плагин

Для начала вспомним, для чего нам нужны плагины? Мой ответ — создание повторно используемого кода, и да — с удобным интерфейсом. Давайте напишем такой код, вот простая задачка: "По клику на параграф, текст должен измениться на красный"

JavaScript и даже не jQuery

Дабы не забывать истоков — начнем с реализации на нативном 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, но еще не плагин

Теперь можно этот код упростить, подключаем jQuery и получаем следующий вариант:

$(function(){
$('p').click(function(){
$(this).css('color', '#ff0000');
})
});

Таки jQuery плагин

С поставленной задачей мы справились, но где тут повторное использование кода? Или если нам надо не в красный, а в зеленый перекрасить? Вот тут начинается самое интересное, чтобы написать простой плагин достаточно расширить объект $.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()". Тут им целые статьи посвятили, дерзайте:

  • http://www.seifi.org/?p=673
  • http://javascript.ru/Arguments
  • http://javascript.ru/Function/apply
  • О обработчиках событий

    Если ваш плагин вешает какой-либо обработчик, то лучше всего (читай всегда) данный обработчик повесить в своём собственном namespace:

    return this.bind("click.mySimplePlugin", function(){
    $(this).css('color', options.color);
    });

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

    // вызовем лишь наш обработчик
    $('p').trigger("click.mySimplePlugin");
    // убираем все наши обработчики
    $('p').unbind(".mySimplePlugin");

    Дежавю? Ок!

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

  • "Essential jQuery Plugin Patterns" [http://coding.smashingmagazine.com/?p=115389]
  • Data

    Если по какой-то причине вы еще не знакомы с "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>

    Animate

    Информация в данном разделе актуальна для jQuery версии 1.8 и выше, если вас заинтересуют возможности расширения для более старых версий, то читайте мою статью "Пишем плагины анимации"

    Для начала затравка – метод "animate()" манипулирует объектом "jQuery.Animation", который предусматривает следующие точки для расширения функционала:

  • jQuery.Tween.propHooks
  • jQuery.Animation.preFilter
  • jQuery.Animation.tweener
  • Начну рассказ с "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) {
    // тут код отвечающий за измение свойств элемента
    }
    }
    }

    Получившийся код будет работать следующим образом:

  • единожды будет вызвана функция "set"
  • функция run будет вызвана N-раз, при этом "progress" будет себя вести аналогично "tween.pos"
  • Теперь, возвращаясь к изначальной задачи по изменению цвета можно наворотить следующий код:

    $.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

    Теперь опять обратимся к 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"; согласно коду, наш метод принимает в качестве параметров следующие значения:

  • x – коэффициент прохождения анимации, изменяется от 0 до 1, дробное
  • t – время прохождение анимации от старта в ms
  • b – начальное значение = 0
  • c – конечное значение = 1
  • d – продолжительность анимации
  • Результат конечно интересен, но его можно ещё чуть-чуть расширить дополнительными функциями (развернем и скомбинируем):

    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 я решил вас не грузить возможностями по расширению библиотеки, но вот время настало… В Sizzle можно расширять много чего:

  • Sizzle.selectors.match
  • Sizzle.selectors.find
  • Sizzle.selectors.filter
  • Sizzle.selectors.attrHandle
  • Sizzle.selectors.pseudos
  • Но браться мы будем лишь за расширение псевдо-селекторов, наподобие:

    $("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]

    Страницы:

    jQuery плагин

    Для начала вспомним, для чего нам нужны плагины? Мой ответ — создание повторно используемого кода, и да — с удобным интерфейсом. Давайте напишем такой код, вот простая задачка: "По клику на параграф, текст должен измениться на красный"

    JavaScript и даже не jQuery

    Дабы не забывать истоков — начнем с реализации на нативном 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, но еще не плагин

    Теперь можно этот код упростить, подключаем jQuery и получаем следующий вариант:

    $(function(){
    $('p').click(function(){
    $(this).css('color', '#ff0000');
    })
    });

    Таки jQuery плагин

    С поставленной задачей мы справились, но где тут повторное использование кода? Или если нам надо не в красный, а в зеленый перекрасить? Вот тут начинается самое интересное, чтобы написать простой плагин достаточно расширить объект $.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()". Тут им целые статьи посвятили, дерзайте:

  • http://www.seifi.org/?p=673
  • http://javascript.ru/Arguments
  • http://javascript.ru/Function/apply
  • О обработчиках событий

    Если ваш плагин вешает какой-либо обработчик, то лучше всего (читай всегда) данный обработчик повесить в своём собственном namespace:

    return this.bind("click.mySimplePlugin", function(){
    $(this).css('color', options.color);
    });

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

    // вызовем лишь наш обработчик
    $('p').trigger("click.mySimplePlugin");
    // убираем все наши обработчики
    $('p').unbind(".mySimplePlugin");

    Дежавю? Ок!

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

  • "Essential jQuery Plugin Patterns" [http://coding.smashingmagazine.com/?p=115389]
  • Data

    Если по какой-то причине вы еще не знакомы с "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>

    Animate

    Информация в данном разделе актуальна для jQuery версии 1.8 и выше, если вас заинтересуют возможности расширения для более старых версий, то читайте мою статью "Пишем плагины анимации"

    Для начала затравка – метод "animate()" манипулирует объектом "jQuery.Animation", который предусматривает следующие точки для расширения функционала:

  • jQuery.Tween.propHooks
  • jQuery.Animation.preFilter
  • jQuery.Animation.tweener
  • Начну рассказ с "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) {
    // тут код отвечающий за измение свойств элемента
    }
    }
    }

    Получившийся код будет работать следующим образом:

  • единожды будет вызвана функция "set"
  • функция run будет вызвана N-раз, при этом "progress" будет себя вести аналогично "tween.pos"
  • Теперь, возвращаясь к изначальной задачи по изменению цвета можно наворотить следующий код:

    $.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

    Теперь опять обратимся к 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"; согласно коду, наш метод принимает в качестве параметров следующие значения:

  • x – коэффициент прохождения анимации, изменяется от 0 до 1, дробное
  • t – время прохождение анимации от старта в ms
  • b – начальное значение = 0
  • c – конечное значение = 1
  • d – продолжительность анимации
  • Результат конечно интересен, но его можно ещё чуть-чуть расширить дополнительными функциями (развернем и скомбинируем):

    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 я решил вас не грузить возможностями по расширению библиотеки, но вот время настало… В Sizzle можно расширять много чего:

  • Sizzle.selectors.match
  • Sizzle.selectors.find
  • Sizzle.selectors.filter
  • Sizzle.selectors.attrHandle
  • Sizzle.selectors.pseudos
  • Но браться мы будем лишь за расширение псевдо-селекторов, наподобие:

    $("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]

    Вернуться к учебному плану