Пишем свой плагин
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]