Как стать автором
Обновить

Комментарии 60

Недавно искал подобное, спасибо.
Буду благодарен за багрепорты на гитхабе.
Вы забыли учесть случай когда элемент по высоте/ширине больше viewport
Ну или outside это не удачное название для такой ситуации
Интересно, в случае если часть эл-та над областью скроллинга и часть под действительно будет возвращаться outside.

Может возвращать что-то из серии «exceeds-vertically»? получится если включены allowMixedStates будет возвращаться exceeds-vertically exceeds-horizontally если блок во все стороны выходит за пределы области видимости
Быстренько пролистал исходник предложенного Вами плагина.

jQuery.viewport лучше:
  • Возможность отслеживать относительно местоположение элемента за пределами области видимости
  • Областью видимости может быть не только окно браузера
  • Более точный и кроссбразуерный вариант функции позиционирующей элемент относительно области вдимости

jQuery.viewport хуже:
  • В случае с scrollspy создается свое событите, с котором можно работать более широко и гибко нежели с моим вариантом трекера, взять тот же one( event, fn )
  • Более высокая производительность, в силу того, что у scrollspy много меньше расчетов по отслеживанию расчетов позиции элемента

В остальном, если брать мой плагин с настройками по-умолчанию, они вроде равны (при первом приближении к коду)
Так же можно добавить к плюсам jQuery.viewport:
— Более точное определение момента вхождения элемента в область видимости, по причине наличия у scrollspy троттлинга.
— Возможность работы в случае использования кастомного скроллбара.
Хмм, куча имён псевдоселекторов, да ещё и не иерархических.
Кстати об иерархичности селекторов, планирую доработать этот момент, вспомнил про иерархичность уже после написания статьи, когда спать ложился, пасибо что напомнил^_^
Так вот, перепроверил, в текущем виде иерархичность селекторов поддерживается более чем полностью, у меня закономерный вопрос: что вы понимаете под иерархичностью?
Под иерархическим я понимаю имя, которое составлятся из категорий от более общих, к более частным.
Возьмём для примера два имени: right-of-viewport, partly-above-the-viewport. Самая общая категория здесь это viewport, соответственно, имя должно начинаться с него. Т.е, если переделать, то это будет viewport-right, viewport-above-partly, или что-то в таком роде (с артиклями или без, по вкусу). При этом все имена заключены в «пространство имён» viewport и далее в субпространства, относящиеся к более мелким категориям (субкатегориям). Такое имя проще генерить, проще получить список всех имён, проще запомнить.

Его сложней читать, это обратная сторона. Если взглянуть на псевдоселекторы CSS, то многие из них написаны в «литературном» порядке, но т.к. их длина меньше, это не так критично. У вас же селекторов много, они однотипные, imo, тут уже лучше иерархический подход.
Я подошел со точки зрения CSS, т.е. читаемости. суть то нейминга селекторов как раз в хорошей читаемости.
В данном случае их крайне много. И иерархическое имя позволяет избавиться от всех отношений типа of, above-the и тд.
Следующий этап — определение местоположения интересующего нас блока относительно области видимости.


Решением будет свой метод, который будет обходить всех родителей вверх по дереву DOM, вплоть до области видимости данного контекста.


Можно использовать голый javascript, чтобы получить координаты относительно области видимости элемента element:

top = element.getBoundingClientRect().top;
bottom = element.getBoundingClientRect().bottom;
left = element.getBoundingClientRect().left;
right = element.getBoundingClientRect().right;

и нет нужды в обходе цепочки родительских объектов.
Вообще в JS практически всегда когда решение включает перебор элементов для получения скалярных данных, вы делаете что-то не так. Браузер уже все посчитал за вас, просто не поленитесь найти где он это показывает.
Стесняюсь спросить, но все же, ткните меня пожалуйста носом в функцию которая будет выдавать мне позицию элемента относительно ближайшего элемента имеющего скроллинг, с учетом того что это не обязательно окно браузера.
Статью то читал? :-) Оно, само по себе, не решает задачу.
да нет, как раз offsetParent помог мне избавиться от parseInt( val, 10 )
Я к тому, что ты это и так использовал уже в своей статье.
Категорически не понимаю чем вас не устраивает:
getFromTop: function() {
	var fromTop = 0;

	for( var obj = $( this ).get( 0 ); obj && !$( obj ).is( ':have-scroll' ); obj = obj.offsetParent ) {
		fromTop += obj.offsetTop;
	}

	return Math.round( fromTop );
}
который написан на чистом javascript, и использует приведенный Вами .offsetParent
Перепутал, думал перебор для оффсета относительно родителя а не предка.
Во первых: у меня и так голый JS используется в том месте
Во вторых:
Начнем, пожалуй, с определения того, что же для данного контекста является областью видимости.
Для моей задачи, а писал я плагин, в первую очередь, для удовлетворения своих нужд, областью видимости является ближайший родитель, имеющий прокрутку.
.getBoundingClientRect() получает позицию элемента относительно окна браузера пользователя, что уже не подходит в случае если если область видимости у нас — не окно, а какой-либо другой блок ниже по дереву DOM.
Во всяком случае сейчас сравнил цифры возвращаемые моим методом и .getBoundingClientRect(), последний возвращает левак какой-то, и я не горю желанием выяснять почему, ибо есть уже работающий вариант функции.
Быстро время редактирования истекает…

А почему нельзя взять bounding rect'ы для viewport и для element и проверить их на наличие коллизий? Координаты там абсолютные, какая разница, от какой они точки.
Выглядит интересно.
Как оно по скорости? Насколько я помню, события на скроле дёргаются довольно часто, что приводит к беспрерывному выполнению привязаного кода.
Сколько элементов на странице можем так отслеживать, до момента начала тормозов?
Полноценные нагрузочные пока не проводил, но да, быстродействие на большом кол-ве отслеживаемых элементов — пока узкое место плагина.

Конкретнее, на 170-200 отслеживаемых эл-тах появляются просадки.

Буду работать в этом направлении, решение, скорее всего, заключается в минимизации количества хэндлеров события scroll
Я когда-то решал схожую задачу. Могу сказать несколько моментов, которые использовал.

1) Проверять в хэндлере сдвиг, хотя бы на 10 пикселей, в ином случае ничего не делать (или делать по отпусканию скрола). Уже уменьшит количество вызовов раз в 10 :-)
Точнее количество вызовов будет тоже, но часть быстро отсеется.

2) Запоминать элементы: нет смысла каждый раз оборачивать $( element ) их, когда можно 1 раз обернуть и пользоваться.
Вот прям щас сделал пункт 2 и делаю пункт 1 :D

+ повысил до 250 элементов, объединив getFromTop и getFromLeft в один метод, сократив кол-во обходов DOM в два раза, странно что я сразу до этого не допер, а только в момент написания статьи=)
Всегда пожалуйста :-)
как вариант — сделать еще троттлинг, даже если сделать 100мс очень сильно сократится кол-во расчетов и обхода дерева элементов в случае скроллинга перетаскиванием ползунка скроллбара.
Ага. не забудь еще бинды сделать на pgup, pgdown, home, end
они ж сами по себе вызывают onScroll, не?
во всяком случае, я когда проверял — все работало, и не помню чтобы onScroll не вызывался этими кнопками.
Вызывают, но может быть ситуация, когда 100мс не прошло, а человек нажал end — ивент с предложеным подходом пропустится и будет странное поведение.
Суть в том, что подобные сдвиги надо внести в исключения, если хочешь ограничивать по времени.
Для начала я бы остановился на сдвигах в пикселях, а не на времени, т.к. там много хитрых ситуаций, которые не сразу отлавливаются.
Подумал ты про бинд этих кнопок в принципе, ели в контексте троттлинга — спасибо учту.

Но пока — я, пожалуй, буду копать в сторону оптимизации работы с набором отслеживаемых эл-тов, это на текущий момент очевидное узкое место плагина.
«250 отслеживаемых элементов хватит всем».
На самом деле представить не могу зачем так много. У меня даже в сложных интерфейсах было, ну максимум, 3 подобных проверки.
Для одинаковых элементов они группируются, так что проблем нет.
Можно пример использования, пожалуйста?
На гитхабе (линк в конце поста) лежит пример, правда, пока, тот который на 170 эл-тах тупить начинает.
Нет, я имел ввиду живой пример. Абстрактно то всё понятно.
Где может понадобится отслеживать отображение такого количества элементов?
А, по количеству отслеживаемых… да честно говоря — фиг знает, но хочется иметь большой задел.
У меня в одном проекте, например, есть лента уведомлений, уведомление считается прочитанным если если оно попало в область видимости, за пределами области видимости может быть до 100 элементов, но как только элемент попадает в область видимости, производится нужная кухня и трэкер отвязывается, так что это не совсем тот пример.

Ссылку на проект дать не могу, да и честно говоря не хочу, в силу того что я пока не очень доволен своим детищем.
уведомление считается прочитанным если если оно попало в область видимости, за пределами области видимости может быть до 100 элементов

Я для подобных уведомлений использовал прочтение по ховеру.

Вообще имеет место следующий подход: во время бинндинга добавляем элемент в массив вида
arr = [
    {
        x: {
            left: int, // левая граница
            right: int // правая граница
        },
        y: {
            top: int,   // верхняя граница
            bottom: int // нижняя граница
        },
        ev: event // событие, когда на экране
    },
    ...
]

Массив отсортирован, в зависимости от текущей задачи, например по y.top
Во время скрола мы проходим только по части массива (где x;y попадает на экран), проверяя нужно ли вызывать ивент.
Небольшим увеличением потребляемой памяти получим возможность отлова событийбесконечного количества элементов, т.к. ресурсов затрачиваться больше не будет (за исключением оперативной памяти).
Сейчас там +- схожий код.

Плагин написал потому что хотел написать что-то более универсальное, что можно приложит вообще к чему угодно.
Просмотрел весь код — не похоже там на +-, скорее похоже на постоянные вычисления всего и вся заного.
«Там» это в моем проекте=)
здесь же ситуевина немного другая, не позволяющая использовать такой подход, ибо он не подходит для отслеживания положения вне области видимости.
Подходит. Просто надо грамотно обходить массив. В любом случае вычислений меньше будет.
В конечном итоге, самым затратным по времени выполнения оказался метод определения позиции элемента.
Увеличил скорость работы плагина просто введя некий аналог кеширования значений на 1 секунду:
getRelativePosition: function( forceViewport ) {
	var fromTop = 0;
	var fromLeft = 0;
	var $obj = null;

	for( var obj = $( this ).get( 0 ); obj && !$( obj ).is( forceViewport ? forceViewport : ':have-scroll' ); obj = $( obj ).parent().get(0) ) {
		$obj = $( obj );
		if( typeof $obj.data( 'pos' ) == 'undefined' || new Date().getTime() - $obj.data( 'pos' )[1] > 1000 ){
			/*
			* Making some kind of a cache system, it takes a bit of memory but helps us veeery much, reducing calculation
			* */
			fromTop += obj.offsetTop;
			fromLeft += obj.offsetLeft;
			$obj.data( 'pos', [ [ obj.offsetTop, obj.offsetLeft ], new Date().getTime() ] );
		} else{
			fromTop += $obj.data( 'pos' )[0][0];
			fromLeft += $obj.data( 'pos' )[0][1];
		}
	}

	return { "top": Math.round( fromTop ), "left": Math.round( fromLeft ) };
}

1 — секунда это не так уж и мало чтобы в значительной степени снизить кол-во вычислений, в то же время это и не так много чтобы прозевать вероятные изменения в DOM.
По идее надо бы вообще вынести этот лимит в конфиг, т.к. по идее программист то знает когда и как часто меняется DOM
Опять же, не будет работать в случае если viewport != window
Если кому интересно, есть стандартный способ детекции элемента во вьюпорте, реализованный в т. ч. Polymer: это lifecycle события enteredViewport и leftViewport.
Они полноценно реализованы в polymer, или полифиле lifecycle-events.

Lifecycle-events также генерит события attached, detached, сигнализирующие о том, был элемент вставлен или изъят из DOM.
Ну, это полимер, я, например, его не использую
lifecycle-events это 2.1kb плагин, он не относится к полимеру.
сам плагин — да, ток вот ему до кучи еще 5 других плагинов надо:
var MO = require('mutation-observer');
var evt = require('emmy');
var matches = require('matches-selector');
var getElements = require('tiny-element');
var intersects = require('intersects');
иии… он содержит всё те же зависимости.
Ах, похоже там все зависимости прямо в плагине и есть. Даже не в сжатом.
Странное использование require — я подумал что оно по другому работает — тащит внешние зависимости.
Не особо листая вниз кода, я подумал как zav, что он тащит внешние зависимости ибо раньше такого использования require я не встречал.

З.ы. И все равно, даже это плагин работатет с viewport=window
З.ы.ы. и нечего тут минусами кидаться, кармы итак нету Т_Т
Извиняюсь, поспешил :) Привык работать в концепции browserify с модульными зависимостями как в node, забыл, что не все в курсе могут быть.
Да, lifecycle-events не такой гибкий, конечно. Но для простой задачи забить гвоздь это отличный молоток :)
Извиняюсь, но что это? У меня звоночек отметил, что на старинной статье в избранном кто-то фигней страдает, а это еще и автор оказывается )
Подглядывал как у хабра работают древовидные комменты, я и забыл что тут их нельзя удалять :D
Ну, главное чтобы для дела )
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации