Низкий FPS при прокрутке страницы. Решение проблемы background-attachment: fixed

    Решил я тут недавно на одном из своих сайтов сделать легкий редизайн. И дошло дело до фона. Показался он мне каким-то скучным. Захотелось его немного «оживить». Подобрал подходящую картинку небольшого размера, загнал ее в свойство фона:

    body{
    	background: url("../images/bg.jpg") no-repeat center center / cover fixed;
    }
    


    и довольный нажал F5. Красота, да и только!

    Начал скроллить страничку вниз и чувствую, что-то не то…


    Такое чувство, как будто я играю в Crysis на очень старом компьютере. Почему же на сайте начались «тормоза» и прокрутка проходит рывками?

    Я начал свое расследование…


    Сначала я погрешил на свойство cover, но дело оказалось не в нем. Отключив фиксированное положение фона (убрав fixed), мой «Crysis» выдал мне больше 30 FPS! «Во дела...», подумал я. Как же так? Почему? Почему я не замечал этого раньше? Возможно, это не очень заметно на легковесных сайтах, где не так много html элементов.

    А дело оказалось вот в чем. Использование background-attachment : fixed каждый раз при прокрутке вызывает операцию перерисовки. Страница должна переместить свое содержимое. И когда дело доходит до фиксированного фона, браузер должен заново прорисовать картинку в новом месте, относительно существующих DOM-элементов.

    Чтобы решить эту проблему, нашему фоновому изображению нужен свой элемент, чтобы оно могло двигаться независимо от других. А также нам понадобится CSS3-свойство will-change. О нем речь пойдет ниже.

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

    Давайте я покажу все на примере.

    Это наш изначальный код (я развернул свойства для наглядности):

    body{
      background: url("../images/bg.jpg") no-repeat center center;
      background-attachment: fixed;
      background-size: cover;
    }
    


    А вот, что нам необходимо сделать для решения проблемы:

    body{
      position: relative;
    }
    body::before {
        background: url("../images/bg.jpg") no-repeat center center;
        background-size: cover;
        content: ' ';
        height: 100%;
        left: 0;
        position: fixed;
        top: 0;
        width: 100%;
        will-change: transform;
        z-index: -1;
    }
    


    Мы добавили position: relative для элемента body, чтобы затем спозиционировать псевдо-элемент, который будет отдельным слоем для нашего фона. Остальные свойства, касательно фона, мы перенесли в ::before. У псевдо-элемента мы теперь используем position : fixed, вместо прежнего background-attachment: fixed у body. Ну и самое важное, без чего вся затея потерпит крах, — это свойство will-change.

    Свойство will-change предписывает браузеру отображать элемент, независимо от окружающих его других элементов. Оно как бы говорит браузеру: «Эй, друг, этот элемент изменится когда-нибудь потом, в будущем, так что прорисуй его только один раз на его собственном слое. И не нужно учитывать остальные элементы — он сам по себе».

    Такие вот дела.

    Данный билд я протестировал в разных браузерах, и вот небольшое резюме:

    1. Google Chrome. Все ОК, работает как часы.
    2. Mozilla Firefox. Все ОК, работает как часы.
    3. Opera. Все ОК, работает как часы.
    4. Safari. Все ОК, работает как часы. За проверку спасибо smssystem
    5. Microsoft Edge. Метод работает, но есть один косяк. Если крутить колесиком, то дергается верх и низ страницы, но потом приходят в норму. Если же крутить с помощью скроллбара, то все ОК.
    6. Internet Explorer. Та же проблема, что и у Edge.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 33

      –30
      Выложи свой код в репозиторий и на сафари, тогда посмотрю. Да я немного ленив :)
        +1
        Всегда знал что фиксированный фон с прозрачными элементами убивает производительность. Спасибо за решение!
          +1
          Вообще говоря, это косяк браузера, который должен уметь поступать аналогично (не перерисовывая все) сам. Но, к сожалению, пока что приходится ему помогать принять правильное решение.

          Спасибо за информацию!
            0
            Спорное утверждение, браузер никому ничего не должен, все оптимизации которые были сделаны, не были каким-то требованием от стандартов. Никто никогда не будет оптимизировать всё «на всякий случай». Если есть желание получить максимум производительности, следует изучить как оптимизации уже сделаны, как браузер на композитные слои разбивает страницу и как рендерит.
            +2
            jsfiddle.net/q8oL13xz/4 — Safari 7.0 работает отлично.
              0
              Спасибо за проверку! Добавил в пост.
                0
                У меня этот пример не тормозит и с
                background: url(«http://www.andicbakim.com/wp-content/uploads/2015/12/website-bg.jpg») no-repeat center center / cover fixed;
                  0
                  Я работаю дома на ноутбуке HP Pavilion 15, не самой мощной конфигурации. И именно на нем я смог отловить подобное поведение. На работе у меня машинка помощнее, и явно заметных проблем с фиксированным фоном я не испытываю (хотя на серьезных сайтах, все-таки, слегка заметное подвисание не радует глаз). Тут дело, скорее, в GPU. Чем лучше, тем менее заметно.
                  Попробуйте открыть какой-нибудь сайт по фильмам или сериалам, где обычно в шапке висит реклама фиксированным фоном (например, хдрезка.ме). Покрутите с ним. Потом снимите в DevTools галку с fixed и посмотрите разницу =) Если разницы не видно, то я вам искренне завидую =)
                    0
                    Да, на хдрезка.ме лаги заметны, но там тормозит не только из за фона =). При отключении, тормоза уменьшились, но остались.
                    Видео на кристалле i5
                0
                Года три назад сталкивался с такой проблемой.
                Интересное решение.
                  +3
                  Свойство will-change предписывает браузеру отображать элемент, независимо от окружающих его других элементов.

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


                  В целом у этого решения (принудительный вынос всего подряд на отдельный композитный слой) есть очень много побочных эффектов: как минимум увеличенное потребление памяти, как максимум — repaint всего слоя на, казалось бы, банальных вещах вроде изменения цвета ссылки внутри слоя по ховеру. Так что следует пользоваться этим аккуратно и следить в DevTools/Web Inspector за аномальным перерисовками.

                    0
                    В общем-то любой неподвижный элемент на странице снижает FPS. Просто на современных машинах это в основном незаметно.
                    Например, на старой машине хабр будет тормозить из-за левой панели с position: absolute;.
                      0
                      Про will-change свойство, написано не совсем правильно, насколько я понимаю, will-change: transform; подсказывает браузеру, что элемент будет двигаться, и ему стоит подготовить для его отрисовки отдельный слой. И мне не совсем понятно, почему в параметрах указан именно transform, ведь по идее как раз transfrom не происходит? Хоть могу ошибаться, и тогда получается, что браузер для скрола использует transform?
                      Что если в качестве параметра для will-change указать opacity к примеру?
                        +2
                        Я схватился за голову и рву себе волосы…

                        Лет десять назад, я делал сайты под IE6. Дизайн и верстка были ужасны, но, практически везде на них я юзал fixed background, который я слизал с руководства по CS:Source. Я делал пачку сайтов именно с тем кодом что предложил автор в начале статьи и ни разу не испытывал проблем. Машины были слабые в то время, 256 мегабайт оперативки и процессор на 1.8 гигагерц.

                        Вопрос: Почему?! Почему ранее это работало идеально и не вызывало проседание FPS?
                          –1
                          Было меньше HTML элементов, картинки были в несколько раз меньше.
                            0
                            Да я не сказал-бы что сейчас все сложнее стало в разы. Вспомнить старые сайты на таблицах, с кучей инлайн стилей…
                            +2
                            Динамика сайтов сейчас колоссальная! UX-дизайнеры порой воротят таких монстров, что хоть стой, хоть падай. Плюс накручено большое количество скриптов, которые в реальном времени постоянно отслеживают и меняют то или иное поведение элементов на странице. Раньше сайты были проще, и трава зеленее =)
                              0
                              Во всех браузерах поменялась модель рендеринга, они оптимизирована под работу с видеокартой. Посчитали лэйаут, отрендерили по тайлам, залили текстуры в видеокарту. Постоянно перегенерировать тайл просто дорого, тогда как раньше перерендеривалось всё постоянно, но без возможности масштабироваться на большие разрешения, в том числе и для картинок с бэкграунда.
                              • UFO just landed and posted this here
                                  0
                                  Я даже напрягся и вспомнил более детально!

                                  Помните в win98 была такая штука — отображение папки как web-страницы? У меня оттуда перекочевала папка с сформированным html документом для отображения. С папки windows была скопирована функция «изменение содержимого данной папки бла-бла-бла», а с руководства по Counter Strike: Source я скопировал background-attachment свойство. Данная папка отображалась у меня через дефолтный windows explorer, а следовательно через IE6?
                                  Далее я вспоминаю о первом сайте для школы, где это свойство использовалось для фотоальбома. Основной браузер в ТЗ (бумажка была даже) был IE6.

                                  За замечание спасибо. Я даже в замешательстве теперь о своей памяти…
                                0
                                Я бы дополнительно порекомендовал применять фон к HTML, как корневому элементу, что дополнительно «разгрузит» body.
                                  0
                                  В свое время столкнулся с этой проблемой при написании своего параллакса.

                                  Несмотря на то, что Вы упомянули, что в самом начале пробовали избавиться от «background-size: cover» и не получили выигрыша в производительности, в моей ситуации отказ от этого свойства решил проблему.
                                    0
                                    К слову, position: relative у body не обязательно указывать, так как position:fixed позиционируется относительно окна браузера (или относительно родителя с transform: translateZ, но это совсем другой разговор)
                                      0
                                      или относительно родителя с transform: translateZ

                                      а расскажи подробнее, что ты имел ввиду?
                                        0
                                        3d трансформации (такие как transform: translateZ(0) или же transform: translate3D(0, 0, 0)) вызывают некоторые побочные эффекты:
                                        Для рендера подключается hardware accelleration, и это чинит некоторые баги с транзишенами или просто когда в сафари часть сайта не отрисовывается.
                                        Создается новый «position scope» (сам придумал, буду признателен, если кто-то подскажет, как это назвать по-человечески) и для дочерних элементов с position: absolute/fixed позиционирование происходит относительно родителя с 3d трансформацией.
                                          0
                                          Про position:fixed относительно родителя не верю :)
                                          Докажешь codepen'ом?

                                          Я вот только после твоего комментария сегодня узнал что браузеры не позволяют одновременно использовать position:fixed, когда у родителя стоит transform: translateZ(0). position: fixed элемент при этом ведёт себя как relative. А ты тут говоришь такое)

                                          Вот смотри, у меня в хроме на вин7, например, не работает.
                                          Баг в хромиуме тянется с 2009 года, который до сих пор не починили. Вон тут некий Эрик разжевывал это ещё в 2011 году.
                                            0
                                            Хех, и правда, и себя запутал, и тебя.
                                            Имел ввиду поведение как position: relative.
                                            Спасибо :)
                                      0
                                      Так-то оно так, только что делать, когда background-attachment:fixed нужно прикрутить к нескольким блокам, а не только к body?
                                        0
                                        Отличное решение, но есть одна неточность.
                                        Позиционирование родительского элемента, когда мы используем position: fixed абсолютно бесполезно. Так как fixed не привязывается к родительским элементам.

                                        Это нас приводит к следующей проблеме — при необходимости сверстать более одного блока с разным фиксированным фоном для каждого из блоков, то последний добавленный фон будет перекрывать все добавленные ранее.

                                        В общем, проблемой это становится только в случае, если имеется более одного изображения.
                                          0
                                          Указали бы что это перевод скринкаста Пола Льюиса :)
                                          https://www.youtube.com/watch?v=QU1JAW5LRKU
                                            0
                                            Хм, честно говоря, я не видел данного скринкаста. А все переводы я непосредственно помечаю специальным шильдиком, можете посмотреть другие мои публикации.
                                              0
                                              Да я образно сказал)
                                              Посмотрите видео, там точно те же идеи озвучены
                                            0
                                            Целая статья посвященная промоутингу элементов в отдельный слой для композитинга? Это ведь по сути web-performance 101, все это можно узнать из любой толковой статьи или курса, в котором объясняют про особенности рендеринга в браузерах и учат как юзать dev tools для профилирования.

                                            Only users with full accounts can post comments. Log in, please.