Pull to refresh

Comments 41

Если это действительно будет работать, то это неимоверно круто. Обязательно проверю на следующем проекте.
В исходники пока не заглядывал, поэтом спрошу вас: callback из afterlag.do(callback) будет ведь выполнен только раз?.. То есть, если у меня динамический контент, то после конца загрузки и вставки контента на страницу я должен создать новый инстанс afterlag, верно?
Да. Колбэк выполняется один раз. Если так случится, что лаги пройдут после создания объекта new Afterlag(), но до создания колбэка, колбэк будет вызван без задержек.

Если контент динамический, создавайте новый инстанс. Я думал о том, чтобы сделать возможность постоянно проверять есть ли лаги, и всякий раз после их окончания вызывать колбэки. Но есть вероятность, что лаги не появятся, и колбэки не вызовутся. А вы их так ждали. Это же лаги, они не надежны. Создавая новый инстанс, даже если лагов нет, колбэки вызовутся.

А как вы в комментарии выделили код строчно, а не блочно?
Через тег <code />.

Всякий раз вызывать коллбек не нужно, в том и суть плагина, как мне кажется, что он делает что-то один раз, как какой-нибудь imagesLoaded-плагин.
а вы пробывали окончанием лагов считать три успешных совпадения времени после обнаружения первого попавшегося лага?
т.е. сначала ждать появления лагов, а потом уже считать совпадения во времени?
Нет. Хорошая идея. Отлично будет сочетаться с передаваемым в настройке значением timeout, который говорит о том, сколько максимум можно ждать окончания лагов. Добавлю такой функционал в скором времени.
Сам себя не похвалишь, никто не похвалит.
Я ваш совет реализовал. И чуток поменял дефолтные настройки. Описание нового свойства в статье выделил как «Обновление статьи».
Возможно, было бы еще неплохо сделать что-то типа такого?
afterlag.on('begin', function() {
    console.log('начались лаги, ждем...');
});

afterlag.on('end', function() {
    console.log('лаги прекратились, исправляем...');
});
Это на случай динамического контента и тому подобного. И да, возможно, лучше было бы использовать requestAnimationFrame? Если кадры пропускаются — значит, анимации точно будет плохо. А если не пропускаются — может, не все так печально?
Чтобы on('begin', ...) имело смысл, нужно оставлять афтерлаг все время включенным. Афтерлаг почти в состоянии сам вызвать лаги, я не хотел бы оставлять включенным всё время работы сайта. Ведь при создании нового инстанса и так можно сказать, что лаги начались, ведь мы их ожидаем. А даже если их нет, это скорее всего никак не повлияет на наши действия. Я стремился сделать плагин максимально казуальным, и в идеале подобрать такие настройки, чтобы никому не пришлось с ними играться.

Если контент динамический, разработчик знает, когда стоит ожидать лагов, после какого именно события. В такой момент стоит создать новый инстанс и ждать прекращения лагов. А on('end', ...) сейчас работает как do(...).

Я слабо разбираюсь в «requestAnimationFrame». Пробежался по этой статье. Похоже, что он в этом плагине будет делать тоже самое, что сейчас делают интервалы. Почему вы считаете, что лучше использовать «requestAnimationFrame»?
Честно говоря, у меня нет простого ответа на этот вопрос. Наверное, потому что создатели браузеров сошлись на том, что requestAnimationFrame лучше подходит для анимаций, т.к. ограничен фреймрейтом и вызывается перед тем, как браузер будет перерисовывать кадр. Все или почти все анимации используют эту функцию, поэтому планировщик браузера может вместо кучи событий таймеров обработать только одно. Поэтому, если лагает эта функция, то лагают и анимации. Если эта функция не лагает, анимации работают плавно. А если лагает setTimeout, это не всегда имеет негативный эффект на анимациях — возможно как раз, что браузер только анимациями и занят.
Звучит убедительно. Подразберусь с «requestAnimationFrame», если там всё действительно так хорошо, заменю интервалы. Спасибо!
Да, кстати, пара комментариев к коду, если вам интересно.

1) свежий gulp сам понимает Gulpfile.coffee, поэтому нет необходимости в промежуточном .js-файле. Он сам вызовет coffee-script/register.
2) @constructor.defaults не самая хорошая конструкция. Может, лучше использовать просто @defaults?
3) for key of first_object — почему не for own key of first_object?
4) new Date().getTime() можно заменить на do Date.now
5) fn = self; self = @ можно заменить на [fn, self] = [self, @]
Конечно, интересно!
1) Не знал.
2) Так не получится. Здесь в любом случае нужно использовать или @constructor.defaults или Afterlag.defaults
# Сейчас в коде так:
class Afterlag
  @defaults:
    delay: 100
    # ...

# Если бы было вот так, нужно было бы использовать @defaults.
class Afterlag
  defaults:
    delay: 100
    # ...

3) Косяк.
4) Забыл про это, давно не работал с датой в JS. Однако, Date.new работает с только с девятого IE. А «requestAnimationFrame», кстати, с десятого. С одной стороны черт бы сними, но все же. А интервалы любой бразуер тянет. Но я все равно покапаю в сторону «requestAnimationFrame».
5) Это синтаксический сахар кофескрипта?
4) браузерная совместимость requestAnimationFrame всегда делается через fallback:
window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       || 
              window.webkitRequestAnimationFrame || 
              window.mozRequestAnimationFrame    || 
              window.oRequestAnimationFrame      || 
              window.msRequestAnimationFrame     || 
              function(){
                window.setTimeout(callback, 1000 / 60);
              };
    })();
2) Да, но зачем в первую очередь использовать @defaults: ...? Если вы посмотрите билд при defaults: ..., там будет Afterlag.prototype.defaults, что вполне себе так же хорошо и здорово, как и Afterlag.defaults, но при обращении к первому варианту не нужно прибегать к дереференсу конструктора. Да, кстати, Afterlag.prototype.defaults еще можно писать, как Afterlag::defaults.

5) Это destructuring assignment, есть в том числе в ванильном ES6.
[a, b, ..., c] = [1, 2, 3, 4, 5, 6, 7] # a == 1, b == 2, c == 7
{a, b, c} = {a: 10, foo: 99, b: 30} # a == 10, b == 30, c == undefined
{a: foo, b: bar} = {a: 265, b: 42} # foo == 265, bar == 42
{@foo, @bar, @quux} = options # @foo == options.foo, @bar == options.bar, @quux == options.quux
2) Мне нравится, что дефолтные значения принадлежат именно к классу, а не к объекту класса. Благодаря этому, я могу в самом начале кода написать Afterlag.defaults.iterations = 5. И потом при создании новых объектов мне не придется каждый раз указывать в настройках, что я хочу 5 итераций. Они возьмутся из нового дефолтного значения.

5) Круто.
2) не совсем уловил разницу между классом и объектом класса. Если вы имеете в виду объект класса (конструктор) и экземпляр класса, то смею вас уверить, свойства в Afterlag.prototype не копируются в экземпляры, а именно наследуются. К примеру,

JavaScript
function Class(foo) {
	if (foo != null) {
		this.foo = foo;
	}
}
Class.prototype.foo = 42;

a = new Class();
b = new Class(100);

console.log('a ->', a.foo); // 42
console.log('b ->', b.foo); // 100
console.log();

Class.prototype.foo = 666;

a2 = new Class();
b2 = new Class(100);

console.log('a ->', a.foo); // 666
console.log('b ->', b.foo); // 100
console.log('a2 ->', a2.foo); // 666
console.log('b2 ->', b2.foo); // 100
console.log();
CoffeeScript
class Class
	foo: 42
	
	constructor: (foo) ->
		@foo = foo if foo?

a = new Class
b = new Class 100

console.log 'a ->', a.foo # 42
console.log 'b ->', b.foo # 100
do console.log

Class::foo = 666;

a2 = new Class
b2 = new Class 100

console.log 'a ->', a.foo # 666
console.log 'b ->', b.foo # 100
console.log 'a2 ->', a2.foo # 666
console.log 'b2 ->', b2.foo # 100
do console.log


Как видите, после обновления prototype, поменялись все экземпляры, у которых есть свойство foo, и которые не переприсвоили его в свой контекст. Если обновление уже существующих объектов нежелательно, то можно написать вот так:
JavaScript
function Class(foo) {
	this.foo = (foo == null) ? this.foo : foo;
}
CoffeeScript
class Class
	foo: 42
	
	constructor: (@foo = @foo) ->
Я ерунду написал. Имел ввиду класс и экземпляр класса, а не объект класса. Как я понимаю, ваш аргумент за defaults: ... заключается в том, что мне в коде самого плагина не придется писать @constructor.defaults. А в остальном все в порядке?

Я конструкцию @constructor.defaults в коде плагина использую только один раз. Зато все пользователи плагина, если захотят изменить дефолтное значение, будут писать Afterlag.defaults.iterations = ..., а в вашем случае придется писать Afterlag.prototype.defaults.iterations = .... Это длиннее, и выглядит не так интуитивно понятно.

Спасибо, что разъяснили про наследование. И примеры у вас замечательные. И комментарии интересные.
Я конструкцию constructor.defaults в коде плагина использую только один раз
Тут, скорее, речь о том, что вы используете неявно определенные свойства, т.е. это эдакие протекающие абстракции. Ведь constructor, к которому вы обращаетесь, и constructor в объявлении класса — это вообще разные вещи, и совпадают у них названия абсолютно случайно. CoffeeScript мог назвать свой конструктор ctor, или так же, как имя класса, или вообще считать конструктором первую определенную в классе безымянную фукнцию (как в LiveScript — рекомендую попробовать). Тогда как @constructor ссылается на свойство конструктора, которое назначается рантаймом автоматически после инстанцирования объекта:
function Class() { }
var a = new Class;
console.log(a.constructor); // [Function: Class]


Спасибо, что разъяснили про наследование. И примеры у вас замечательные. И комментарии интересные.
Сарказм? :) Учитывая, что комментарии просто максимально информативны — # 666, # 100 и # 42.
Я вас в пердыдущем комментарии совершенно искренне благодарил и делал комплименты. Я действительно из ваших комментариев много нового узнал и старого закрепил. Так что вы про меня плохо не думайте, это я вам от чистого сердца!

А по поводу @constructor.default я понимал, что он не связан тем конструктором, который в объявлении класса. Собственно по-этому я его в коде верно и использовал. Если бы вместо @constructor.defaults я использовал Afterlag.defaults никаких неявно определенных свойств и протекающих абстракций бы не было. Но мне не хотелось внутри класса обращаться к классу по имени, я хотел обратиться через какой-то универсальный указатель. Сейчас посмотрел документацию к кофескрипту, там, и вправду, нигде не говорят, что так можно делать. Значит я этот способ где-то в другом месте вычитал. Может и стоит заменить это на Afterlag.defaults, но мне по прежнему не хочется к классу обращаться по имени внутри этого же класса.
Да, я понимаю ваш дискомфорт от этого — я сам такой же. Вы не рассматривали вариант с дефолтными объявлениями не в отдельном объекте, а прямо в теле класса? Поясню на примере (без JS, он тут уже будет некрасивым):
class MyClass
	foo: 42
	bar: null
	buzz: 'meow'
	quux: 3.14
	
	constructor: (opts) ->
		do => @[key] = value for own key, value of opts # merge options
		
		# do something useful...
	
	toString: -> "foo: #{@foo}, bar: #{@bar}, buzz: #{@buzz}, quux: #{@quux}"

a = new MyClass
console.log "#{a}" # foo: 42, bar: null, buzz: meow, quux: 3.14

b = new MyClass foo: 666
console.log "#{b}" # foo: 666, bar: null, buzz: meow, quux: 3.14

c = new MyClass foo: null
console.log "#{c}" # foo: null, bar: null, buzz: meow, quux: 3.14

Обновления дефолтов, соответственно, производить через MyClass::bar = 'okay, new bar field'.
Не думал об этом, но мне таксой способ не нравится, да и мой способ устраивает на все 100. Я плагины начинаю писать с их вызова. Представляю, что есть какой-то идеальный плагин, который используется именно так, как я хочу его использовать. Пишу пару примеров использования еще не существующего плагина. А потом пишу этот плагин, ограничивая себя рамки того, как я хочу его использовать. Так вот в моём идеальном плагине дефолтные настройки не должны изменяться так: Afterlag.prototype.defaults.iterations = 5. Слишком длинная строка.
Да, кстати, если будете разбираться — посмотрите заодно, setTimeout в DOM Worker-е как-то коррелирует с лагами браузера? :)
Worker'ы в отдельном потоке отработают и отдадут результат. В итоге основной поток не будет ззатронут «лагами» Worker'ов.
Тут, скорее, о том, чтобы получить более независимую оценку общебраузерных лагов. Возможно, что если лаги дотянулись до воркера, то плохи дела у самого браузера.
Так лаги-то в большинстве случаев не из-за того, что браузер плохо спроектирован, а из-за того, что скрипты кое-как написаны. Костыль, описанный в топике, скорее добавляет этих самых лагов. Нужно не оборачивать проблему в красивую обертку, а решать ее. :)
Наткнулся на очень понятную статью про «requestAnimationFrame». С этой штукой хорошо создавать анимацию, потому что как говорится в статье:
1. Анимация будет более плавной, так как браузер может оптимизировать её
2. Анимация в неактивной вкладке будет приостановлена, позволяя процессору отдохнуть
3. Более энергоэкономна.

Таким образом использовать его в «Afterlag.js» не нужно. Если афтерлаг будет видеть лаги чуть дольше, чем они есть, ничего страшного. А вот если афтерлагу раньше времени покажется, что лаги кончились, всё будет очень плохо. С «requestAnimationFrame» хорошо создавать плавную анимацию, а я буду использовать более глючный «setInterval» для поиска лагов.
Offtop: однажды я выключил javascript в настройках браузера и поразился тому, насколько быстро стали отображаться все сайты. Мораль: поменьше анимаций, параллаксов и прочих рюшечек, назначение которых раздражать пользователя и затормаживать браузер.
Рюшечки это ведь далеко не всегда прихоть программиста или дизайнера. Иногда заказчик вложив $XXX отказывается от простого «фасада». Конечно в дальнейшем все эти рюшечки убираются под гнетом негативного фидбэка, но первую версию почти всегда приходится делать обвешанной.
Рюшечки бывают очень даже полезными. И для юзабилити, и для красоты. Главное, чтобы они были созданы не только для того, что быть, а несли в себе какой-то смысл, имели цель своего существования.
Если не ошибаюсь, в Angular и Angular Light специально стоит задержка пока приложение не инициализируется, отключающая директиву (ng|al)-animate. Очень удобно.
Правда я для виртуализированного списка еще отключаю al-animate на время обновления контейнера с элементами, благо в scope.$scan есть callback
А если комп медленный, одноядерный и замученный фоновыми процессами — оно вообще не включится?
В настройках можно передать параметр timeout, время в миллисекундах которое вы готовы ждать до окончания лагов. То есть, даже если лаги не кончились афтерлаг запустит все колбэки, если лаги длятся дольше, чем значение преданное в timeout
На самом деле JavaScript не совсем однопоточный, а только в пределах одной страницы, так ведь?
Это, кстати, тема для большой и сложной статьи, на самом деле. Жаваскрипт всех страниц, как правило, работает в одном потоке, потому что DOM Worker-ы — исключение. Но при вызове какой-то из страниц метода типа alert или confirm происходит магия, при которой блокирующий вызов в контексте страницы становится блокирующим только для страницы, но сам браузер не блокируется.
Понравился плагин на практике. Спасибо автору!
Пожалуйста! Рад, что вам пригодилось.
Sign up to leave a comment.

Articles