Опубликован: 28.02.2016 | Доступ: свободный | Студентов: 1888 / 174 | Длительность: 11:48:00
Лекция 10:

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

< Лекция 9 || Лекция 10: 123 || Лекция 11 >

О обработчиках событий

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

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

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

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

Дежавю? Ок!

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

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) {
// тут код отвечающий за измение свойств элемента
}
}
}

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

  1. единожды будет вызвана функция "set"
  2. функция 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>
< Лекция 9 || Лекция 10: 123 || Лекция 11 >
Наталья Маркова
Наталья Маркова
Ярослав Гаевой
Ярослав Гаевой

10 марта 2016 c 20:13 до 22:39 я сдавал экзамен. Однако, за два месяца статус не изменился: "Задание не проверено"

Когда ожидать проверки?