Несколько интересностей и полезностей от веб-разработчика *

  • Tutorial
* Надеюсь, ilusha_sergeevich не обвинит меня в плагиате.
Если что, пост переименую.

[Тут была картинка для привлечения внимания]



Всем привет! За время работы постепенно накапливаются наработки, которыми можно было бы поделиться с сообществом. Но ни одна из этих наработок не тянет на большой полноценный пост. Поэтому я собрал все мелочи, что вспомнил, в одной статье: несколько простых опен-сорц проектов, пара советов и находок. Каждый из предложенных скриптов в этой статье поставляется как есть, под лицензией WTFPL (кроме Балалайки). С радостью приму пулл реквесты с исправлением багов или изменениями в README.


donut.js — микро-библиотека, рисующая бубликовые (donut) и круговые диаграммы


image
Во время работы над очередным проектом, появилась задача нарисовать много информативных бубликов на карте мира, и не просто нарисовать, а еще и поддержать ИЕ8, который, как известно, не умет SVG, а только безобразный VML. Первое, что приходит в голову, это Raphael. Порывшись некоторое время, я нашел это решение. К сожалению, автор проявил изобретательность простым хаком: на круговой диаграмме (pie chart) он нарисовал белый круг. Это решение не подошло, так как дырка бублика должна быть прозрачной. Изучение возможности рисования при помощи Raphael таких диаграмм мне показалось чересчур трудоёмким. Остальные скрипты на просторах интернета мне так же не подошли. Пришлось писать свой костыль, взяв за основу математику рисования арок этого проекта. Арки для VML версии нарисованы используя элемент arc.
var myDonutDiv = donut(options);


Функция donut возвращает div с классом donut, содержащий бублик в виде SVG или VML. Список опций:
  • el (Node) — куда вставлять бублик (опционально)
  • data (Array) — массив данных (объектов вида {value: 42, name: 'some name'})
  • size (Number) — диаметр булика (по умолчанию 100)
  • weight (Number) — толщина линии (диаметр бублика минус диаметр дырки) (по умолчанию 20)
  • colors (Array) — список цветов (по умолчанию, массив из одного элемента: ['#555']). Если цветов больше, чем данных, цвета повторяются.

Пример:
var myDonut = donut({
  el: document.getElementById( 'container' ),
  size: 150,
  weight: 30,
  data: [{
    value: 1,
    name: 'A'
  },{
    value: 2,
    name: 'B',
    customData: 'Yeah' // об этом ниже
  },{
    value: 3,
    name: 'C'
  },{
    value: 4,
    name: 'D'
  }],
  colors: [ '#80a8cc', '#da3b3e', '#ffa921', 'red' ]
});


Если толщина арки равна половине диаметра бублика, получается круговая диаграмма:
image

У функции donut есть два метода:
setColor(arc, color) — установка цвета для арки
data(arc[, data]) — получение или установка данных для арки

Аргумент arc — DOM узел (path для SVG и arc для VML), который можно получить, обратившись к элементу с селектором [data-name="имя"], где имя — значение name из данных.
B_Arc = document.getElementById( 'container' ).querySelector( '[data-name="B"]' )
donut.data(B_Arc).customData; // получение данных, в том числе, кастомных
donut.setColor(B_Arc, '#8dc700'); // установка цвета


Для того, чтоб добавить текст, просто вставьте узел с текстом в возвращаемый блок:
text = myDonut.appendChild( document.createElement('span') );
text.className = 'donut-text';
text.innerHTML = '3';


.donut-text {
  position: absolute;
  left: 0;
  line-height: 150px; // диаметр бублика
  width: 150px;  // диаметр бублика
  text-align: center;
  font-size: 70px;
}


Позицию можно задать в CSS или используя свойство style.

Решение работает в восьмом осле. В устаревших ослах не проверялось, но у меня нет причин полагать, что в них не сработает. Скрипт не зависит от наличия других библиотек. Удобную реализацию вставки текста, тултипов и пр. оставляю за вами.

Живой пример
Репозиторий


Балалайка


Балалайка — крошечная jQuery-подобная DOM библиотека, ужатая до предела, имеющая малый, но достаточный для vanilla.js-пасанов набор методов. В её основу вложена идея того, что для «получения элемента по ID» не нужно подключать jQuery. Крошечный размер позволяет встраивать её куда угодно.

Например, вместо подключения jQuery, как основную библиотеку:
<script>
$=код_балалайки
</script>

Полный код
<script>
$=(function(n,e,k,h,p,m,l,b,d,g,f,c){c=function(a,b){return new c.i(a,b)};c.i=function(a,d){k.push.apply(this,a?a.nodeType||a==n?[a]:""+a===a?/</.test(a)?((b=e.createElement(d||"div")).innerHTML=a,b.children):(d&&c(d)[0]||e).querySelectorAll(a):/f/.test(typeof a)?/c/.test(e.readyState)?a():c(e).on("DOMContentLoaded",a):a:k)};c.i[f="prototype"]=(c.extend=function(a){g=arguments;for(b=1;b<g.length;b++)if(f=g[b])for(d in f)a[d]=f[d];return a})(c.fn=c[f]=k,{on:function(a,d){a=a.split(h);this.map(function(c){(h[b=
a[0]+(c.b$=c.b$||++p)]=h[b]||[]).push([d,a[1]]);c["add"+m](a[0],d)});return this},off:function(a,c){a=a.split(h);f="remove"+m;this.map(function(e){if(b=(g=h[a[0]+e.b$])&&g.length)for(;d=g[--b];)c&&c!=d[0]||a[1]&&a[1]!=d[1]||(e[f](a[0],d[0]),g.splice(b,1));else!a[1]&&e[f](a[0],c)});return this},is:function(a){b=this[0];d=!!b&&(b.matches||b["webkit"+l]||b["moz"+l]||b["ms"+l]);return!!d&&d.call(b,a)}});return c})(window,document,[],/\.(.+)/,0,"EventListener","MatchesSelector");
</script>


(похоже на вставку какого-то счетчика, только немного массивнее)

Как известно, обычный GET запрос требует время и немалого трафика. Инлайновая вставка позволит сэкономить драгоценные миллисекунды до полной загрузки страницы.

Или в качестве локальной переменной для вашего скрипта:
(function($) {
  //...
})(код_балалайки)

Полный код
function($) {
    // your code starts here
    $(function() {
        $('.my-selector').on('click', function() {
            alert('I need my balalaika');
        });
    });
  // your code ends here
})((function(n,e,k,h,p,m,l,b,d,g,f,c){c=function(a,b){return new c.i(a,b)};c.i=function(a,d){k.push.apply(this,a?a.nodeType||a==n?[a]:""+a===a?/</.test(a)?((b=e.createElement(d||"div")).innerHTML=a,b.children):(d&&c(d)[0]||e).querySelectorAll(a):/f/.test(typeof a)?/c/.test(e.readyState)?a():c(e).on("DOMContentLoaded",a):a:k)};c.i[f="prototype"]=(c.extend=function(a){g=arguments;for(b=1;b<g.length;b++)if(f=g[b])for(d in f)a[d]=f[d];return a})(c.fn=c[f]=k,{on:function(a,d){a=a.split(h);this.map(function(c){(h[b=
a[0]+(c.b$=c.b$||++p)]=h[b]||[]).push([d,a[1]]);c["add"+m](a[0],d)});return this},off:function(a,c){a=a.split(h);f="remove"+m;this.map(function(e){if(b=(g=h[a[0]+e.b$])&&g.length)for(;d=g[--b];)c&&c!=d[0]||a[1]&&a[1]!=d[1]||(e[f](a[0],d[0]),g.splice(b,1));else!a[1]&&e[f](a[0],c)});return this},is:function(a){b=this[0];d=!!b&&(b.matches||b["webkit"+l]||b["moz"+l]||b["ms"+l]);return!!d&&d.call(b,a)}});return c})(window,document,[],/\.(.+)/,0,"EventListener","MatchesSelector"));



Так как Балалайка наследуется от массива, в распоряжении разработчика наличествуют все методы массивов:


Кроме этого, у Балалайки есть jQuery-подобные методы on, off, is, extend и событие загрузки документа:
$('.my-selector').on('click.namespace', function() {
    alert('I need my balalaika');
});


$('.my-selector').off('click.namespace');

$('.my-selector').on('click', function(evt) {
    if($(evt.target).is('.another-selector')) {
        alert('I need my balalaika');
    }
});

var myObject = {a:1};
$.extend(myObject,{
    b: 2
});

$(function() {
    // Do something with DOM
});


Такой, на первый взгляд, скудный набор методов, позволяет делать многое. А из-за простоты кода и ванильных функций, балалайка сильно выигрывает у массивных библиотек в производительности.

Несколько примеров

Парсинг:
var elements = $('<div><span class="yeah"></span></div>');

Поиск одного элемента в другом:
var myElement = $('.my-selector', node);

Установка стиля:
$('.my-selector').forEach(function(el) {
    $.extend( el.style, {
        width: '30px',
        backgroundColor: 'red'
    });
});

Делегирование события:
$('.my-selector').on('click', function(evt) {
    var node = evt.target;
    while(node !== this) {
        if($(node).is('.delegated-selector')) {
            // Handle it!
            break;
        }
        node = node.parentNode;
    }
});

Простой плагин:
$.fn.addClass = function( className ) {
    this.forEach( function( item ) {
        var classList = item.classList;
        classList.add.apply( classList, className.split( /\s/ ) );
    });
    return this;
};

Я не предлагаю отказываться от jQuery, просто не забывайте, что есть микро-библиотека со смешным названием. Балалайка используется во фреймворке Матрешка (статьи о Матрешке)

Библиотека поддерживается всеми браузерами, начиная с ИЕ9.
Ссылка на репозиторий


Функция procrastinate


Позвольте мне лениво процитировать самого себя.
Представьте себе следующую ситуацию (взята из моей практики). У вас есть форма с некими текстовыми полями: чекбосами и пр. Когда меняется значение одного из элементов формы, приложение должно отправить запрос на сервер, который, в свою очередь, возвращает данные для рендеринга трёх графиков. Рисование графиков — дело тяжелое для процессора и на слабом компьютере занимает полсекунды (Highcharts он такой). Теперь представьте пользователя, которому скучно и он решил многократно кликнуть на чекбокс. Что произойдет? Отправится куча запросов, вернется куча ответов, которые так же многократно отрисуют график. Что обычно делают в таком случае? Отменяют запрос на сервер. Спрашивается: зачем было этот запрос посылать, если можно было дождаться, пока тот угомонится? :)

Для решения этой задачи я использовал простейшую функцию (возможно, велосипед), которая принимает другую функцию в качестве аргумента и возвращает её модификацию, которая может быть запущена только однажды за определенный промежуток времени. Пример:
var doSomethingHeavy = function( i ) {
	console.log( 'Ok', i );
};

var procrastinateSomethingHeavy = procrastinate( doSomethingHeavy );

for( var i = 0; i < 100; i++ ) {
	procrastinateSomethingHeavy( i );
}

// >> Ok 100

Код функции
var procrastinate = function ( f, d, thisArg ) {
	var timeout;
	if( typeof d !== 'number' ) {
		thisArg = d;
		d = 0;
	}
	return function() {
		var args = arguments,
			_this = this;
		clearTimeout( timeout );
		timeout = setTimeout( function() {
			f.apply( thisArg || _this, args );
		}, d || 0 );
	};
};

Метод, кроме «прокрастинирующей» функции, принимает задержку, и контекст в качестве аргументов. Задержка отвечает за то, на сколько миллисикунд будет отложен реальный вызов функции при очередной попытке её вызова.

А вот пример случая, когда функция никогда не будет вызвана (для лучшего понимания).
var procrastinateSomethingHeavy = MK.procrastinate( function() {
	console.log( 'Ok' );
}, 1000 );

setInterval( function() {
	procrastinateSomethingHeavy();
}, 500 ); // интервал меньше задержки

ссылка на gist

Состояние чекбокса indeterminate


image
А знали ли вы о том, что чекбокс имеет «среднее» состояние? Я не знал. Таки да, имеет! Оно задаётся исключительно с помощью JavaScript:
checkbox.indeterminate = true;

А обратиться к чекбоксу из CSS с таким состоянием можно используя псевдокласс :indeterminate.

Подробнее опсевдоклассе
Статья на английском (даже если не знаете языка, код говорит сам за себя)


Противный зловред


В поисках конкретного выпуска интернет-шоу This is Хорошо на сайте, выдающем себя за официальный (или действительно на официальном, но взломанном), я наткнулся на страницу «МВД Украины», где меня обвинили в просмотре порно с малолетками и прочим гадостям. На странице появилось незакрывающееся окно с текстом о том, что должен заплатить штраф, отправив СМС на определенный номер. Я и так и так пытался закрыть страницу, не получалось… В итоге, я откоючил сеть на ноуте и несколько раз перезагрузил Хром, чтоб очистить хеш. Естественно, мне стало любопытно, как скрипту удалось потрепать нервы опытному веб-разработчику, и я открыл эту страницу в прекрасном браузере, который называется links (если что, это консольный браузер).

Код зловреда был таков:
onbeforeunload=function(){
	location.reload();
	return "Ваш браузер був заблокований з міркувань безпеки.Вся інформації на вашому комп'ютері заарештована. Усі  ваші файли зашифровані.  Протягом 12 годин кримінальну справу буде передано до суду. Однак, у Вас ще є 12 годин, щоб заплатити штраф, - таким чином, Ви можете уникнути пред'явлення кримінальної справи."
};

onload=function(){
	location.reload();
}

Страница пытается перезагрузиться, и здесь срабатывает обработчик onbeforeunload, который так же пытается перезагрузить страницу, вызывая бесконечное появления глупого текста.

Как попробовать зловред у себя в браузере? Создайте локально HTML файл со слудующим содержимым:
<html>
  <head>
    <title></title>
	<meta charset="utf-8"> 
    <style></style>
  </head>
  <body>
	<script>
		onbeforeunload=function(){
			location.reload();
			return "Текст зловреда"
		};



		onload=function(){
			location.reload();
		}
	</script>
  </body>
</html>

Откройте страницу в Хроме (в Файерфоксе зловред не такой зловредный) и наслаждайтесь. Когда надоест, удалите или переименуйте файл.


Идея: mousedown вместо click


UPD: Нужно быть аккуратным с этим подходом. Подробнее в комментариях.
Хотите субъективно увеличить скорость срабатывания интерфейса? Замените событие click на mousedown, если это возможно (если нет драг-н-дропов и событий двойного щелчка). Это позволит избежать задержки в десятые или сотые доли секунды, прежде чем пользователь отпустит кнопку мыши. Пользователю вашего приложения покажется, что интерфейс работает шустрее, так как срабатывать код будет немного быстрее, чем моментально (исключая тяжелые операции и запросы на сервер).

Небольшая демка.


Совет: меньше используйте CDN


Запрос библиотеки — это сложный HTTP запрос. Разработчики с опытом это знают и кладут весь JS код в один файл с помощью Grunt, r.js или другого сборщика. Это позволит сэкономить время на лишних запросах и, часто, трафик, так как GET даже с ответом 304 весит немало. Кроме этого, CDN может не отвечать. Программисты часто решают это failback-ом, но это плохой вариант, так как браузер не сразу поймет, что сервер лежит, а пользователю прийдется наслаждаться загружающейся страницей несколько драгоценных секунд.


Редактор SuitUp и чистка WYSIWYG от мусора


Есть такой редактор, написанный мной, который сделан в виде jQuery плагина, называется SuitUp. Самым большим минусом редактора явлется отсутствие чистки результирующего HTML от вставляемого форматированного текста, например, из Врода: от стилей, спанов, служебных комментариев и прочей ерунды.

Представляю небольшую функцию (тоже jQuery плагин, но с ванильным содержанием), которая с помощью методов DOM, без регулярок, рекурсивно чистит любой contenteditable блок от комментариев, от тегов XML, SCRIPT, STYLE, LINK, META, от атрибутов style и align. «Чистельщик» создан неделю назад, поэтому не исключены баги и неучтенные случаи грязного HTML.

jQuery( editor ).on( 'paste', function( evt ) {
	setTimeout( function() {
		jQuery( this ).mswordFilter();
	}.bind( this ) );
});

Ссылка на Gist


Простейший полифил для addEventListener


Восьмой осёл, как известно, не умеет метод addEventListener, у него есть небогоугодный attachEvent. Для программистов, которым не нужны кастомные события и прочие навороты, предлагаю свой небольшое полифил, используемый в Матрешке.

Ссылка на AMD версию
Сылка на обычную версию


Репорт об опечатках на сайте


Есть такая замечательная система оповещений об опечатках на сайте, называемая Orphus. Пользователь, найдя опечатку или неточность может выделить соответствующий текст и нажать Ctrl+Enter. Открывается окно, в котором юзер может ввести комментарий к ошибке, а разработчик, затем, логинясь на сайте Орфуса видит все оповещения и, если хочет, исправляет ошибки. Мне захотелось написать свою оповещалку для опечаток для страницы документации Матрешки. За основу я взял известный хак отправки формы на страницу гуглоформы, который не требует от разработчика поднятия собственного сервера.

Как подключить?
1. Создайте Гугл-форму с тремя полями: текст с ошибкой, комментарий пользователя, ссылка на страницу. Пример.
2. Используя веб инспектор, исследуйте форму. Нам нужно значение атрибута формы action и имена инпутов (атрибут name)
3. Подключите скрипт перед
4. Запустите функцию:
typo({
    formURL: FORM_ACTION_URL,
    selectionName: NAME_OF_SELECTION_INPUT,
    commentName: NAME_OF_COMMENT_INPUT,
    pageName: NAME_OF_URL_INPUT
});


Например:
typo({
    formURL: 'https://docs.google.com/forms/d/1sQhv81wN65__7H4quwhDbecvtUxzAGZ-lMmlwF9MKcc/formResponse',
    selectionName: 'entry.1972481987',
    commentName: 'entry.1777335671',
    pageName: 'entry.339184258'
});

Репозиторий
Живая демка
«Опечатки» из демки будут появляться в этой таблице.

В идеале, скрипту нужно дожидаться готовности DOM и кастомное окошко (у самого руки не доходят). Если это кто-то сделает, бросайте ссылку, с удовольствием форкну :)
Скрипт не зависит от наличия сторонних библиотек.


vanillatree — ванильная замена jstree


image
jstree — jQuery плагин, создающий вложенное дерево списков. Меня этот плагин не устроил сложным, громоздким API. vanillatree — компактная замена jstree, не требующая подключения какой-либо сторонней библиотеки. В список фич входит: кастомное контекстное меню для каждой ветки, выбор ветки открытие или закрытие дерева под веткой по клику (или используя методы), перемещение ветки, удаление ветки и соответствующие события.

Как использовать?

Подключите соответствующий JS и CSS файл и, когда DOM дерево будет готово, создайте инстанс VanillaTree
var tree = new VanillaTree(treeElement, options);

Первым аргументом должен быть элемент или селектор элемента, вторым — необязательные опции:
placeholder (String) — показывается, когда дерево пустое
contextmenu (Array) — список объектов, отвечающих за контекстное меню ({label: метка, action: действие})

var tree = new VanillaTree('.my-selector', {
  placeholder: 'None of leafs is added yet',
  contextmenu: [{
    label: 'Label 1',
    action: function(id) {
      // someAction
    }
  },{
    label: 'Label 2',
    action: function(id) {
      // someAction
    }
  }]
});


После инициализации, в дерево можно добавлять листья методом add.
tree.add({
  label: 'Label A', // текст ветки
  id: 'a', // ID ветки, опционально, если не задан, будет рандомным
  parent: 'b', // ID родителя (опционально)
  opened: true, // открыта ли ветка по умолчанию (опционально)
  selected: true // выбрана ли ветка по умолчанию (опционально)
});


Вот полный список методов:
  • add(options) — Добавляет ветку
  • move(id, parentId) — Перемещает ветку к другому родителю
  • remove(id) — Удаляет ветку с заданным id
  • open(id) — Раскрывает ветку
  • close(id) — Закрывает ветку
  • toggle(id) — Закрывает или открывает ветку в зваисимости от текущего состояния
  • select(id) — Выбирает ветку с заданным id


VanillaTree вызывает кастомные события
  • vtree-add — добавление ветки
  • vtree-move — перемещение ветки
  • vtree-remove — удаление ветки
  • vtree-open — открытие ветки
  • vtree-close — закрытие ветки
  • vtree-select — выбор ветки


«Ванильное дерево» использует метод dispatchEvent и конструктор CustomEvent, если это возможно, для генерации кастомных событий. ID ветки, на которой сработало событие содержится в объекте evt.detail. Все события всплывающие, т. е. вы можете слушать события конкретной ветки в родителе, родителе родителя,… и так далее, до document.
treeElement.addEventListener('vtree-open', function(evt) {
  info.innerHTML = evt.detail.id + ' is opened';
});

treeElement.addEventListener('vtree-close', function(evt) {
  info.innerHTML = evt.detail.id + ' is closed';
});

treeElement.addEventListener('vtree-select', function(evt) {
  info.innerHTML = evt.detail.id + ' is selected';
});

//...


Обратите внимание, что vanillatree использует Балалайку в качестве «локальной библиотеки». Взгляните на 197 строку.

Скрипт работает во всех современных браузерах, включая ИЕ10 (накануне я заменил dataset на getAttribute/setAttribute, a CustomEvent на initEvent). Для ИЕ9 нужен полифилл classList.

Ссылка на живой пример
Ссылка на репозиторий

Всем добра!

Какой мини-проект следует дальше поддерживать (улучшить документацию, написать больше примеров, добавить фич)?
Как вы относитесь к vanilla.js?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 55
  • +29
    Картинка для привлечения внимания очень сильно привлекает внимание!
  • 0
    Пардон за битые якоря, уже исправлено.
    • +17
      Полезные инструменты, спасибо. Но совет с mousedown может и навредить. Я например, после того как случайно нажал кнопку, отвожу фокус от элемента и отпускаю кнопку, чтобы действие не сработало.
      • 0
        Очень прошу автора статьи вообще убрать этот «совет» из статьи. Ни в коем случае нельзя заменять click на mousedown. Не у каждого пользователя есть мышь, а как же клавиатура и тач устойства.
        • 0
          Я написал ниже, что чекбокс должен быть чекбоксом. Просто не очень удачный пример.
          • 0
            Заменил пример в статье на этот: jsbin.com/wifowi/2/
            • 0
              Неважно какой элемент. Вы упускаете суть, ваша демка не будет работать на устрйствах, в которых есть только клавиатура, и на некоторых тач устройствах. Вот вам пример — jsbin.com/vacubazehogu/4, попробуйте клавитурой на кнопки понажимать.
              • 0
                Как правило, элементы интерфейса — не только кнопки и чекбоксы. Совет именно для тех случаев, для которых, вам, в любом случае, прийдется писать код для работы с клавиатуры.
                • 0
                  Приведите пример таких элементов интерфейса тогда.
                  • 0
                    Вы серьезно? Ютуб, например.
                    • +1
                      Отличный элемент интерфейса «Ютуб».
                      • 0
                        Очень остроумно. Конечно же я имел в виду элементы интерфейса ютуба.
                        • 0
                          Я не просто так просил уточнить. Если вы считаете, что play/pause//mute/fullscreen это не кнопки, то вы ошибаетесь.
                          • 0
                            Я говорю о главной странице, списке рекомандаций, списке подписок и пр.
                            • 0
                              К ссылкам применяется то же правило. Вот вам пример со ссылками, вместо кнопок — jsbin.com/radurahivofa/1#, попробуйте в нем клавиатурой понажимать.
            • +1
              Ни в коем случае нельзя заменять click на mousedown. Не у каждого пользователя есть мышь, а как же клавиатура и тач устойства.

              Просто совет не полный. Необходимо одно событие Click заменить на несколько: mousedown, keydown и touchstart. И еще хорошо подумать: не приведет ли такое «умное» срабатывание к негативным последствиям. Например, если в результате работы скрипта выполняется переход на другую страницу, то обработчик должен быть только на Click, а если нужно обновить часть текущей страницы, то можно и Down события использовать. А можно и более просто сформулировать: результат выполнения скрипта пользователь увидит менее, чем за 0.5-1 секунду, то можно Click поменять на Down.

              Правда подобные приемы сокращения времени отклика обычно применяются в десктопном или мобильном софте, а для Web они мало где заметны, т.к. время отклика как правило большое.
              • 0
                Необходимо одно событие Click заменить на несколько: mousedown, keydown и touchstart.


                Да, только надо не забыть вызвать stopPropagation на touchstart и mousedown, чтобы они вместе не сработали. И убедиться, что keydown сработал только один раз (при залипании от срабатывает несколько раз).

                В идеале, если уж так сильно хочется ускорить веб приложение этим методом, то надо байндить и mousedown и click. И в клике проверять, сработал ли недавно mousedown. И при этом, надо не забыть про ghost click (событие click сработает на некоторых тач устройствах на 350-500ms позже чем mousedown/touchstart).

                А если хочется ускорить приложение еще больше, то можно еще следить за событием mouseover, и начинать выполнять действие в нем. Или вообще следить за траекторией движения мыши, и предугадать куда пользователь кликнет.

                Проще говоря, изначальный совет «замените click на mousedown и увеличьте скорость приложения» не очень корректный, и может больше навредить, чем помочь.
                • 0
                  Проще говоря, изначальный совет «замените click на mousedown и увеличьте скорость приложения» не очень корректный, и может больше навредить, чем помочь.


                  А так абсолютно с любым советом. Моим самым любимым примером неудачного применения полезных практик является перегрузка паттернами. Например: The Abuse of Design Patterns in writing a Hello World Program.

                  Все как всегда: сначала думаем как сделать, потом думаем нужно ли оно, и только потом делаем.
          • 0
            Бублики на фото выглядят очень аппетитными, да.
            • +1
              А бублики-то NSFW…

              п.с. Спасибо, интересно!
              • +3
                Господа, после минуса в карму, прошу написать сюда комментарий о том, что именно вам так не понравилось.
                • +5
                  у вас катарсис в начале статьи — нарушение всех законов драматургии. В результате читатель лишь разочаровывается — такое прекрасное начало, а дальше какие-то скудные библиотеки и плагины.
                  • +1
                    Пожалуй, мне стоит сделать паузу в публикациях на Хабр. Сказать, что я разочарован — ничего не сказать.
                  • 0
                    Я минусы ставить не умею, но за рекомендацию mousedown вместо клик и за тотальный отказ от CDN веб-разработчика можно люстрировать, ИМХО. :-)
                    • +2
                      Соглашусь, вопрос с событиями спорный, но по поводу CDN настою по следующим причинам:
                      1. Их слишком много. Утопичный вариант загрузки библиотеки лишь однажды для всех сайтов в интернете не сработает.
                      2. Версий библиотек тоже немало. Вероятность того, что пользователь, зайдя на ваш сайт, будет иметь в кеше именно ту версию библиотеки мал.
                      3. Лишний HTTP запрос, который не только кушает трафик, но и сам по себе медлителен.
                      4. Сервер может не отвечать. О фоллбеках я написал в статье.
                      5. Нет статистического исследования на тему того, насколько CDN увеличивает скорость загрузки страницы. Учитывая первые два пункта, предполагаю, что скорость только падает.
                      6. В большинстве случаев люди используют CDN из-за банальной лени и неумения собирать проект в один файл, который, кстати, при первом заходе пользователя закешируется а при следующем, вернется с кодом 304, если страница, сайт или приложение покажется интересным.
                      7. CDN просто-напросто может закрыться, вам прийдется обновлять все проекты, но, перед этим, потерять две трети юзеров.
                      • 0
                        Возможно, для каких-то проектов это будет справедливо. Но для большинства типовых сайтов подгрузить jquery с крупного CDN будет весьма удобно и выгодно. Вряд ли бы CDN получили такое распространение, если бы ухудшали производительность сайтов. Естественно, к их использованию надо с умом подходить, а не подключать в качестве «CDN» виртуалку вашего соседа – тогда ничего внезапно не закроется.
                        • 0
                          3. Лишний HTTP запрос, который не только кушает трафик, но и сам по себе медлителен.

                          jsDelivr умеет конкатинировать библиотеки в один запрос
                          • 0
                            Идея CDN в том, что вы загружаете библиотеку на одном сайте, а на другом она вернется с кодом 304. jsDelivr конкатенирует несколько разных библиотек в одну, и вероятность того, что на двух сайтах один и тот же набор библиотек — мал. Т. е. единственное спорное преимущество CDN против одного локального файла (в котором, кстати, не только библиотеки, но и скрипты, отвечающие за работу приложения) пропадает.
                      • 0
                        Не минусовал, но есть пара замечаний.

                        Вот в целом мне статья показалось полезной. В чем-то спорной (например, onmousedown и CDN), точнее, слишком категоричной (ситуации бывают разные, иногда CDN — добро, иногда — не очень). Но в целом интересно было поглядеть указанные вами итемы, про ту же балалайку я не слышал вообще.

                        Но кое-что сильно мешает — используемая лексика. Я ни разу не педант или граммар-наци, но уже привык, что в нормальных статьях (а обзор — тоже статья) используется профессиональная лексика без жаргонизмов и слов вроде «пасаны». Да и в целом стиль у вас похож на разговор с пацанами, а не на статью на хабре. Это бывает уместно, но сильно зависит от тематики.

                        За исключением этих двух пунктов, большое спасибо = )
                        • 0
                          По поводу пацанов — это шутка. Лично мне нравится читать статьи не перегруженные серьезностью.

                          По поводу CDN я уточнил в этом комментарии а этот комментарий говорит о еще одном аргументе против CDN.

                          CDN замечателен для «пруф-оф-концеп» страниц. Для продакшна серьезного проекта с более чем пятью пользователями нужно складывать все скрипты в один файл.
                      • +4
                        Ожидал, что цифра на бублике как то связана с бэкграундом.
                        • +4
                          procrastinate — это вы изобрели debounce, как его назвали в underscore
                          Как правило, таким способом отслеживают завершение ресайза окна, либо в автоподсказках в инпутах.

                          Загляните сюда
                          Пример с ресайзом окна
                          function debouncer( func , timeout ) {
                             var timeoutID , timeout = timeout || 200;
                             return function () {
                                var scope = this , args = arguments;
                                clearTimeout( timeoutID );
                                timeoutID = setTimeout( function () {
                                    func.apply( scope , Array.prototype.slice.call( args ) );
                                } , timeout );
                             }
                          
                          }
                          
                          
                          $( window ).resize( debouncer( function ( e ) {
                              // do stuff 
                          } ) );
                          

                          • +3
                            Задача лежит на поверхности, я не удивлен.
                          • 0
                            > Совет: меньше используйте CDN

                            Меня больше интересует вопрос безопасности, на сегодняшний день развелось много разных CDN, а ведь если какой-нибудь банк или любой другой сайт разместит js с моего CDN, то я смогу точечно производить атаки — внедрять зловред и т.п. для конкретного ip адреса (клиента), и это будет происходить не заметно.

                            PS: у меня нет своего CDN.
                            • +3
                              Я считаю, что все потенциальные риски не сравнимы с единственным спорным неисследованным преимуществом.
                              • +1
                                Кстати, это можно назвать добровольным XSS :)
                              • 0
                                Вот за наводку на mousedown спасибо!
                                Действительно, как-то не обращал внимания на такую мелочь, а разница заметная.
                                • +1
                                  Спасибо за отзыв. Но как написали выше, это не всегда гуд.
                                • 0
                                  Совет с mousedown не учитывает клавиатуру. Например, в демке не срабатывает второй чекбокс при нажатии на пробел.
                                  • 0
                                    Это лишь демка. Чекбокс должен оставаться чекбоксом, а такое поведение можно реализовать для кастомных вещей.
                                  • 0
                                    По поводу msword-filter: для представления списков Word почему-то использует параграфы вместо тегов UL и LI, поэтому их надобно парсить. Посмотрите в моей статье пример реализации. Буду рад, если мой код окажется полезным.
                                    • 0
                                      Хотите субъективно увеличить скорость срабатывания интерфейса? Замените событие click на mousedown, если это возможно (если нет драг-н-дропов и событий двойного щелчка). Это позволит избежать задержки в десятые или сотые доли секунды, прежде чем пользователь отпустит кнопку мыши.

                                      Други, никогда так не делайте! У меня, к примеру (думаю, не только у меня), есть такая привычка — прижать кнопку мыши на кнопке в интерфейсе, а потом окончательно подумать, хочу ли я ее нажимать. Причем что издревле раздражало в вебовских кнопках — так это то, что когда ты прижимаешь мышью обычную кнопку и уводишь мышь в сторону, то она просто отжимается обратно. А когда так делаешь на веб-странице, то кнопка часто начинает drag-drop-иться.
                                      • 0
                                        Переименовал «совет» в «идею» и указал на критику в комментариях.
                                      • 0
                                        Хочу добавить несколько слов про addEventListener:
                                        1. Приведённая в статье библиотека — это не полифил, как бы автор её не называл, т.к. она не реализует функционал addEventListener'ра.
                                        1.1. Библиотека не умеет handleEvent:
                                        let application = {
                                            name: 'test',
                                            handleEvent() { console.log(this.name) }
                                        }
                                        document.addEventListener('click', application);
                                        

                                        1.2. Библиотека не умеет «захват» событий
                                        1.3. Библиотека не заработает с любым отложенным исполнением обработчика события в IE8, т.к. в IE объект event «очищается» сразу после вызова обработчика:
                                        document.addEventListener('mousemove', function(event) {
                                            console.log(event.type);// В IE8 тут будет ошибка
                                        }.throttle(200));
                                        


                                        2. Библиотека не заработает в IE7, во первых потому что в IE7 нету window.Element, а во вторых, потому что window.Node (который можно использовать вместо window.Element) — это не js-объект/конструктор, и его prototype не распространяет свои свойства на потомков.

                                        3. Вероятно библиотека вызовет утечки памяти в IE8, т.к. ссылка на элемент сохраняется в скоупе обработчика события.
                                        • 0
                                          > Приведённая в статье библиотека — это не полифил, как бы автор её не называл, т.к. она не реализует функционал addEventListener'ра.
                                          Она реализует простейший функционал addEventListener: нет поддержки кастомных событий, нельзя передать объект с handleEvent (кстати, что за синтаксис такой?). Её можно назвать простейшим полифилом, который был сделан в противовес массивным «полноценным», но бажным полифилам. Я не нашел ни одного достойного, пробовал, в том числе, и ваш.
                                          > Библиотека не умеет «захват» событий
                                          Капчуринг — проблема, да.
                                          > .throttle(200)
                                          Простите, что это за метод? ES5/6?
                                          > Библиотека не заработает в IE7
                                          В статье говорится о восьмом осле (который называют «устаревающим», но не устаревшим, к сожалению). Под седьмой сейчас мало кто пишет, как и под шестой, а тот, кто пишет, ни в коем случае не должен использовать ваниллу. Использование популярной библиотеки, типа jQuery — обязательно.
                                          > Вероятно библиотека вызовет утечки памяти в IE8, т.к. ссылка на элемент сохраняется в скоупе обработчика события.
                                          Мне нечего возразить. Ослы «славятся» своей очисткой памяти от мусора. Постараюсь от этого избавиться.
                                          • 0
                                            > кстати, что за синтаксис такой?
                                            Если вы про ObjectLiteral то это es6. Есть про handleEvent — то это стандартный обработчик, определённый в стандарте addEventListner'а developer.mozilla.org/en-US/docs/Web/API/EventListener

                                            >> .throttle(200)
                                            > Простите, что это за метод? ES5/6?
                                            Практически «стандартное» расширение прототипа функции, которое есть во многих библиотеках. Например sugarjs.com/api/Function/throttle
                                            Похоже на вашу функцию procrastinate, которая, кстати, тоже не заработает с этой библиотекой.

                                            >> Библиотека не заработает в IE7
                                            > В статье говорится о восьмом осле
                                            В описании гиста написано:
                                            Simple addEventListener polyfill for IE7 and IE8

                                            >> Вероятно библиотека вызовет утечки памяти в IE8, т.к. ссылка на элемент сохраняется в скоупе обработчика события.
                                            > Мне нечего возразить. Ослы «славятся» своей очисткой памяти от мусора. Постараюсь от этого избавиться.
                                            Моя версия полифила, хотя и громоздкая, но практически не вызывает утечек памяти. Конечно, полностью от утечек избавится не удалось, но минимизировать их удалось.
                                            • 0
                                              > «стандартное» расширение прототипа
                                              Я тоже когда-то гадил в прототипы стандартных конструкторов :)
                                              • 0
                                                Object.defineProperty(Function.prototype, 'myFunction', {enumerable: false, value: function(){ /* do something cool */ }})
                                                Благодаря использованию enumerable:false, этот способ не является опасным, в отличие от Function.prototype.myFunction=function(){}

                                                Да и не так страшно расширение прототипов стандартных объектов, как его малюют, главное подходить с умом.
                                                • 0
                                                  Ну для функций проблемы enumerable не существует, как по мне.

                                                  У меня есть большой проект, где я яростно расширял прототипы. Сейчас смотрю в этот код и путаюсь, как и в вашем. Уменя сразу возник вопрос, откуда у function есть метод throttle: стандартный он или встроенный.
                                        • 0
                                          От «балалайки» у меня чуть вывих челюсти не случился =) Чувствую, скоро перестану использовать jQuery в разработке =)

                                          Самое интересное, что в большинстве своём из всей jQuery используется в лучшем случае четверть функционала. Собственный опыт разработки показал, что большая часть кода уходит в:
                                          — Защиту от дурака: проверки типов переменных, наличия требуемых библиотек и так далее
                                          — Методы/функции для сокращённого вызова и для обратной совместимости: сокращённые варианты вызова метода с теми или иными параметрами, устаревшие методы/функции

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.