60 FPS? Легко! pointer-events:none!

http://www.thecssninja.com/javascript/pointer-events-60fps
  • Перевод
  • Tutorial


Вы, наверное, уже читали интересную статью о том, как можно отключать эффекты :hover при скроле – это позволяет здорово сохранить отзывчивость сайта, но имеет один недостаток – вам приходится опираться на один общий класс, и это плохо.

.hover .element:hover {
  box-shadow: 1px 1px 1px #000;
}

Плохо, потому что данный подход ограничивает вашу расширяемость, и вносит лишнюю спецификацию в, и без того нелегкие, селекторы CSS.

Фишка тут в том, что при скроле вы просто удаляете класс .hover с тега body, тем самым отключая все ваши селекторы с :hover-ом. После окончания события, класс возвращается, и эффекты :hover опять в деле.

Круто. Но не очень – привычное переключение глобального класса запускает немалый пересчет стилей, и это не есть гуд. Гениальное решение предложил Christian Schaefer:



О да, pointer-events – наше все!


Свойство pointer-events позволяет управлять условиями, при которых элементы вашей страницы будут реагировать на события мыши. Смотрите сами:



Ошеломительная разница, не так ли? И все это без лишней сложности селекторов:

.disable-hover {
  pointer-events: none;
}

Просто добавляем этот класс к нашему body по началу скрола, и все – мышь «пролетает»!

var body = document.body,
    timer;

window.addEventListener('scroll', function() {
  clearTimeout(timer);
  if(!body.classList.contains('disable-hover')) {
    body.classList.add('disable-hover')
  }
  
  timer = setTimeout(function(){
    body.classList.remove('disable-hover')
  },500);
}, false);

Код, как видите, довольно простой. Вешаем обработчик на событие скрола, в котором сперва сбрасываем предыдущий таймер, проверяем наличие класса на нашем body, и, если его нет – добавляем. Затем просто добавляем новый таймер, который, через пол секунды после окончания скрола, сбросит наш класс. Все!

Почти


Если где-то в странице будут элементы со стилем pointer-events: auto, они перетрут глобальный класс, и будут все еще тормозить. Мы же не хотим этого, верно?

.disable-hover,
.disable-hover * {
  pointer-events: none !important;
}

Как вы догадались, решение, так же, простое. Супер-селектором * с флагом !important мы научим наши элементы вести себя хорошо.

Можете убедиться в работе данного подхода. Попробуйте замерить производительность с hover-ом, и без. Результат впечатляет!
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 53
  • +2
    По-моему, это круто! По крайней мере имеет практическое применение.
    • +3
      Google Chrome 31.0.1650.57, OS X. Разницы в производительности нет.
      • +2
        Google Chrome Windows 7. Весьма ощутимая разница.
        • 0
          Странно, вот матрица браузеров, которые поддерживают pointer-events.
          • +3
            Тем не менее, тоже Win7+Chrome — прирост очень ощутимый. В разы плавнее скролится.
            • 0
              А что странно, никак в толк не возьму? Хром же поддерживает.
              • 0
                Извините, только сейчас заметил, что промахнулся с ответом. Хотел ответить уровнем выше.
          • 0
            Google Chrome 31.0.1650.57, OS X. Разница ощутимая.
            • 0
              Что прямо в отладчике видно разницу?
            • +6
              Надо было так:
              Mac Pro, 2х 6-core Xeon, 64 gb RAM, Google Chrome 31.0.1650.57, OS X. Разницы в производительности нет.
              • 0
                Версию OS X указать забыли
                • –1
                  А что есть сомнения в версии OS X на Mac Pro?
              • 0
                Я думаю тут дело в мощности процессора или видеокарты. Я тоже разницы не заметил.
                • 0
                  Все аналогично и разницы тоже нет. 60 fps.
                • +2
                  Может я что-то не так делаю, но FF25 Linux тоже никакой разницы не вижу.
                  • +6
                    Аналогично. Что так тормозит, что так тормозит.
                    • +3
                      FF25 + Win7 — аналогично, совершенно никакой разницы в FPS. Судя по наблюдениям, в FF25 уже есть встроенный механизм отключения :hover во время скролинга.
                      А вот на «хромовых» браузерах разница, хоть и небольшая, но есть. К тому же они даже с включенным pointer-events скролят в разы плавнее, чем FF25
                    • 0
                      Win8, новая Opera. Производительность выросла более чем в 2 раза.
                      • 0
                        Хром 30, Убунту, железо старенькое. Никакой разницы.
                        • 0
                          fps подрос, но субъективно особо разницы то и нету… проверю потом на всякий айфонах андроидах, глядишь там толк есть.
                          • НЛО прилетело и опубликовало эту надпись здесь
                            • 0
                              Android точно может быть с мышой… однако это достаточно экзотическая конфигурация…
                              • +3
                                при легком касании на iOS делается hover
                                • +2
                                  На андроиде тоже происходит hover при касании, например на огромных списках с кнопками, если у вас есть для кнопки :active :hover — такие стили могут существенно понижать производительность, потому что рендер затыкается на элементе по которому случайно тапнули, чем заставили перерисовать стиль.
                                • 0
                                  Мыши там может и нету, но события мыши эмулируются. И hover там так же есть, как есть и active. С другой стороны их редко для тач девайсов используют. Хотя и то что в примере так же редко встречается.
                            • –1
                              В итоге, я так понял, все ховеры на странице тормозят браузер.
                              • –8
                                В реализации на jquery ещё и сам код становится простым и коротким. Красота.
                                • +2
                                  Если вы гонитесь за производительностью, зачем вам jQuery?
                                  • +2
                                    Иногда jQuery уже есть в проекте. Чем плохо использовать его и для подвешивания на scroll()?
                                    • +2
                                      jQuery сам по себе медленный. Перед тем, как оптимизировать мелочи типа прокрутки, желательно заниматься крупными оптимизациями более очевидных вещей.
                                      • 0
                                        Оптимизировать надо узкие места.
                                        Фреймворки позволяют экономить время, и их замена на самопальный код — огромное количество человекочасов, причем на развивающемся проекте оно почти всегда не укладывается в количество доступных ресурсов.

                                        Данный хак прост и с точки зрения «улучшение производительности / затраченное время» он однозначно выигрывает.
                                  • +1
                                    в реализации на underscore/lodash скорее, ибо вам нужен debounce. Добавлять обработчики событий нативной, классы и т.д. много кода вам не сократят. Да и то это так же лишнее…
                                    • +5

                                      да?
                                      • +3
                                        Уже осознал на собственной шкуре карме — хабражители не любят jQuery. Был не прав.
                                    • –1
                                      Opera 18.
                                      При отключении hover, и промотке страницы скролом, перестают подсвечиваться элементы пока не пошевелишь мышь, при включении — подсвечиваются.
                                      • +1
                                        А ведь можно добавить это в пользовательский скрипт и получить высокую производительность на всех сайтах!
                                        • 0
                                          Похоже, я сказал какую-то несусветную глупость, потому что совершенно не знаю JS. Кто-нибудь пояснит мне, в чем она состоит?
                                          • +1
                                            в том что эта штука помогает только в очень специфичных кейсах.
                                            • 0
                                              Но при этом она вроде бы ничего не ломает. Невелика беда, что во время прокрутки hover не работает.
                                              • 0
                                                В этом специально подготовленном примере fps выросло в полтора раза (у меня), а вот на всех моих текущих проектах толку вообще нету.
                                        • +1
                                          В Firefox тормозит как с хаком, так и без. Да ещё и включаются события заторможенно.
                                          • 0
                                            Возникает мрачное подозрение, что перед нами ещё один такой рецепт, который превосходно работает в Chrome, но дурно (или никак) в Файерфоксе. Тем более, что Paul Irish явно сослался на какой-то чисто хромовский нюанс.
                                          • +3
                                            В FF тормозит в обоих случаях.
                                            В chrome чуть быстрее, чем без
                                            В IE11 работает очень шустро, разницу не заметил
                                            /Win8.1 64 bit, intel core i5
                                            • +1
                                              Если навешивать на hover эффекты, вызывающие repaint, то будет разница. Если такого нет, то и разницы особой не заметите. Даже на графиках в Dev Tools.
                                              • –1
                                                Спасибо! Грядёт ускорение барона, и эта фишка туда тоже попадёт.
                                                • +1
                                                  Очень полезно! Сделал расширение для хрома, который применяет данный твик на всех сайтах. Надеюсь, что пригодится кому-то еще.
                                                  • 0
                                                    «которое», конечно же. Уже нельзя исправить.
                                                  • 0
                                                    А разве pointer-events вообще применим к HTML? Когда я последний раз смотрел w3c, он был разрешён только для SVG, а из HTML убран из-за каких-то проблем с совместимостью.

                                                    Некоторые браузеры применяют и к HTML вопреки спецификациям, но далеко не факт, что это будет работать везде и всегда.
                                                    • 0
                                                      один косяк: когда скролл остановился, то элемент остался не подсвеченным
                                                      • 0
                                                        А для IE 8 — 10 можно попробовать что-то такое (внимание, не тестировалось!):

                                                        .no-hover:before {
                                                        position: fixed;
                                                        top: 0;
                                                        left: 0;
                                                        z-index: 5000;
                                                        width: 100%;
                                                        height: 100%;
                                                        display: block;
                                                        content: '';
                                                        }
                                                        

                                                        // Used to track the enabling of hover effects
                                                        var enableTimer = 0;
                                                        
                                                        /*
                                                         * Listen for a scroll and use that to remove
                                                         * the possibility of hover effects
                                                         */
                                                        window.addEventListener('scroll', function() {
                                                          clearTimeout(enableTimer);
                                                          removeNoHoverClass();
                                                        
                                                          // enable after 1 second, choose your own value here!
                                                          enableTimer = setTimeout(addNoHoverClass, 1000);
                                                        }, false);
                                                        
                                                        /**
                                                         * Removes the hover class from the body. Hover styles
                                                         * are reliant on this class being present
                                                         */
                                                        function removeNoHoverClass() {
                                                          document.body.classList.remove('no-hover');
                                                        }
                                                        
                                                        /**
                                                         * Adds the hover class to the body. Hover styles
                                                         * are reliant on this class being present
                                                         */
                                                        function addNoHoverClass() {
                                                          document.body.classList.add('no-hover');
                                                        }
                                                        
                                                        • 0
                                                          linux amd64, chromium
                                                          разница есть, процентов 5 =)

                                                          Однако у меня выключен плавный скролл (терпеть его не могу, возможно даже как раз изза того что тормозит). С ним разница больше, примерно как на видео в шапке. Однако без плавного скролла все быстрее всё равно. Кто придумал плавный скролл и как его отключить глобально у всего человечества знает кто? =))
                                                          • 0
                                                            По-моему при каждом скролле обращаться к элементу проверяя есть ли у него класс это жестоко. Вот так получше будет.

                                                            var body = document.body,
                                                                timer,
                                                                hover_disabled = false;
                                                            
                                                            window.addEventListener('scroll', function() {
                                                              clearTimeout(timer);
                                                              if( ! hover_disabled && ! body.classList.contains('disable-hover')) {
                                                                body.classList.add('disable-hover');
                                                                hover_disabled = true;
                                                              }
                                                              
                                                              timer = setTimeout(function(){
                                                                body.classList.remove('disable-hover');
                                                                hover_disabled = false;
                                                              }, 500);
                                                            }, false);
                                                            
                                                            • 0

                                                              Мне это кажется или хром теперь по умолчанию себя так ведёт без всяких костылей?

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