image
Рисунок используемый для parallax эффекта. Автор Patryk Zabielski
Привет друзья, я покажу вам как создать простою многослойную иллюстрацию с глубиной, которая переходит к контенту. Мы будем использовать метод, в котором необходим только css и чистый JS(coffeescript) (Никаких jQuery!).
Этот урок для начинающих, с начальным знанием JS и CSS, так что я буду объяснять большинство вещей и ссылаться на внешние источники.
Финальное демо

Подготовим илюстрацию


Давайте начнем с того, что нарежем иллюстрацию на слои. Лучшим вариантом развития событий будет, то что вы сами нарисовали или у вас есть доступ к исходному файлу со всеми слоями.
Если нет, то не волнуйтесь мы разберемся с этим.
Я буду работать с картиной, которую нарисовал в прошлом году. Если у вас нет возможности создать свою, мы можете скачать мои исходники перед тем, как мы начнем.

image
Визуализированный концепт слоев в 3D пространстве

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

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

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

image
На картинке справа есть незаметная линия, которая даст нам лучший переход в следующую секцию

Начнем кодить


Подготовление

Что вам потребуется:

  • Свежий проект в Codepen (Если вы хотите следовать коду ниже, то не забудьте изменить HTML на HAML, CSS на SCSS и JS на Coffeescript, в настройках Codepen)
  • Начальные знания HAML & Sass(SCSS)
  • Базовое понимание Javascript и Coffeescript (Если у вас все еще плохо с этим — я рекомендую «Javascript & jQuery” by Jon Duckett) Если вам не хочется работать с Coffeescript, то вы можете автоматически конвертировать его в JS и вставить фрагменты кода ниже чтобы получить чистый JS
  • Ваши иллюстрация/фото разделенная на несколько png слоев

Давайте начнем с HTML структуры. Мы сделаем контейнер-родитель и дадим ему ID 'hero'. Потом мы добавим несколько div, с классом 'layer' и data-type атрибутом со значением 'parallax'.
#hero
    .layer{“data-type” => “parallax”}
    .layer{“data-type” => “parallax”}
    .layer{“data-type” => “parallax”}
    .layer{“data-type” => “parallax”}
    .layer{“data-type” => “parallax”}

Добавим базовых стилей. Начнем с #hero. Я установил высоту иллюстрации 800px
#hero {
  height: 800px;
  overflow: hidden;
  position: relative;
}

Теперь, перейдем к стилизации повторяющего класса слоев. У каждого из них будет одинаковая высота, как у #hero и позиционирование, и мы добавим position: fixed
.layer {
  background-position: bottom center;
  background-size: auto;
  background-repeat: no-repeat;
  width: 100%;
  height: 800px;
  position: fixed;
  z-index: -1;
}

Следующая вещь, которую мы хотим сделать это добавить картинки, которая мы подготовили до этого. Мы создадим еще несколько классов, по одному для каждого слоя. После чего добавим ссылку на картинку внутри свойства background-image.
.layer-01 {
   background-image: url('ссылка на картинку');
}
.layer-02 {
   background-image: url('ссылка на картинку');
}
// и т.д.

Не забудьте обновить HTML и назначить классы к нужным div в нашем порядке, первым слой это задний фон, следующие слои будут накладывать поверх друг-друга
#hero
  .layer.layer-01{“data-type” => “parallax”}
  .layer.layer-02{“data-type” => “parallax”}
  .layer.layer-03{“data-type” => “parallax”}
  .layer.layer-04{“data-type” => “parallax”}
  .layer.layer-05{“data-type” => “parallax”}

Время JavaScript

Добавим методом, который будет проверить если пользователь начал прокручивать страницу вниз.
window.addEventListener ‘scroll’, (event)

EventTarget.addEventListener() метод регистрирует слушателя на EventTarget. event target может быть элемент в document, сам document, объект Window или любой объект поддерживающий события (такие как XMLHttpRequest)

Сохраним количество пикселей, которое проскролено вертикально от начала страницы в переменную topDistance. Чтобы сделать это, будем использовать свойство pageYOffset
window.addEventListener ‘scroll’, (event) ->
  topDistance = @pageYOffset

После этого, мы выберем все слои в нашей илюстрации и сохраним их в переменную layers. Для этого мы будем использоваться методом querySelectorAll и data атрибут внутри элементов HTML, который мы установили до этого.
window.addEventListener ‘scroll’, (event) ->
  topDistance = @pageYOffset
  layers = document.querySelectorAll("[data-type='parallax']")

Следующее что мы сделаем, это пройдемся циклом через все слои и применим свойство transform к каждому из них. Но перед этим, мы укажем еще одну вещь внутри нашего HTML файла, атрибут data-depth. Он даст нам возможность контролировать как быстро элемент будет двигаться, давайте не будем глубоко углубляться в значения, мы вернемся к этому позже.
#hero
  .layer.layer-01{“data-type” => “parallax”, "data-depth" => "0.10"}
  .layer.layer-02{“data-type” => “parallax”, "data-depth" => "0.20"}
  .layer.layer-03{“data-type” => “parallax”, "data-depth" => "0.50"}
  .layer.layer-04{“data-type” => “parallax”, "data-depth" => "0.80"}
  .layer.layer-05{“data-type” => “parallax”, "data-depth" => "1.00"}

Чтобы пройтись через все элементы, мы будем использоваться цикл for. Начнем наш цикл с создания переменной, где мы будем хранить наши слои. После чего возьмем значение из data-depth атрибута, который мы указали внутри нашего HTML. Далее, мы вычислим передвижение слоев, умножив дистанцию скрола от начала страницы на значение атрибута data-depth данного слоя. Элемент со значение 1.0 будет двигаться нормально с остальной страницей, вы можете воспринимать его, как элемент с выключенным parallax.

Последнее что мы сделаем, это обновим окончательное значение движения для параметра transform translate3d слоя, чтобы сделать это мы будем использовать свойство style вместе со всеми вендорными префиксами для transform.

Чтобы код был более читаем и DRY, мы сохраним параметр translate3d в переменную translate3d

for layer in layers
  depth = layer.getAttribute(‘data-depth’)
  movement = -(topDistance * depth)
  translate3d = 'translate3d(0, ' + movement + 'px, 0)'
  layer.style['-webkit-transform'] = translate3d
  layer.style['-moz-transform'] = translate3d
  layer.style['-ms-transform'] = translate3d
  layer.style['-o-transform'] = translate3d
  layer.style.transform = translate3d

Теперь когда мы указали data-depth=»1.00" элемент будет двигаться вместе со страницей, как обычный элемент без parallax эффекта. Все значение меньше чем 100 будут иметь эффект parallax.

Телефоны

Для телефонов мы отключим parallax версию и заменим ее на статичную картинку, чтобы сохранить производительность. Чтобы сделать это, создадим новый div ниже нашего div #hero, с id hero-mobile и применим к нему display: none вместе с параметрами background и height.
#hero-mobile {
  display: none;
  background: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/272781/full_illustration.png") no-repeat center bottom / cover;
  height: 320px;
}

Чтобы показывать его вместо parallax, будем использовать media query и применять параметр display: none к десктопной версии, переопределяя наш display: none на display: block для #hero-mobile.
@media all and (max-width: 640px) {
  #hero {
     display: none;
  }
  #hero-mobile {
     display: block;
  }
}

Финальный код

Комментарии (23)


  1. acupofspirt
    02.04.2016 18:26

    В хроме(51.0.2697.0 canary) при скролле мышкой коряво выглядит.


  1. dshster
    02.04.2016 21:13
    +7

    Интересно! Но может уже пора слазить с CoffeScript в угоду того же ES6, иначе уже как-то дико код выглядит? И, я так понимаю, ко всему этому прелоадер нужен будет, а то послойная загрузка картиное не очень красиво смотрится.


  1. Alexufo
    02.04.2016 23:32
    +6

    Меня смущает только одно, автор не видел, что размер его слоев по полтора мегабайта каждый?
    Вроде бы давно не секрет, что нельзя большие картинки из фотошопа сохранять в png24 без последующего пережатия той же pandapng.
    Берем https://s3-us-west-2.amazonaws.com/s.cdpn.io/272781/ilu_03.png
    Итог: -80%!
    Было 1.3 MB стало 256.2 KB. И вроде жить можно.


    1. FrozenInternet
      03.04.2016 10:20
      +2

      Да, да. Как-то уж больно долго демка картинки грузит.
      Конкретно к автору. Уже после «Подготовим илюстрация» начали возникать вопросы. Вы хоть раз прочитали статью, если вы ее переводили? Такое ощущение, будто после google transtate-а ее ник-то не ревьюил.
      Теперь по поводу статьи. Как я понял, статья рассчитана на новичков. Почему тогда coffescript, sass (хотя больше обычного css я тут не увидел) и тем более HAML? Странно это, учитывая то, что вначале статьи обещали «чистый css и js». :)


      1. meam
        03.04.2016 10:22
        -1

        Опечатки принято отправлять в личные сообщения. 2 часть это претензия к автору, для меня это тоже большой вопрос, но он обещал лишь чистый js без jQuery


        1. dbanet
          03.04.2016 14:35
          +2

          Всем и так известно, что опечатки принято отправлять в личных сообщениях.

          Наверное, если FrozenInternet сообщил о них не в ЛС, а в комментариях, он хотел этим что-то сказать?

          А, да нет, тут даже ни о чём думать надо: он прямым текстом сообщает вам:

          Вы хоть раз прочитали статью, если вы ее переводили? Такое ощущение, будто после google transtate-а ее ник-то не ревьюил.
          , что с вашей статьёй что-то сильно не так, и в таком виде ей тут не место.

          По ощущениям, только херовые переводчики размахивают ссаными "опечатки-в-ЛС" и "это-не-ко-мне-это-к-автору". И публика жрёт всё: статьи всё равно в плюсе. Самоуправление работать перестало. Впрочем, той самой публики и так осталась десятая часть.


    1. dom1n1k
      03.04.2016 15:06
      -2

      ФШ жмет нормально, обычно после него дополнительные оптимизации без потери качества не превышают нескольких процентов. Если -80%, то это совершенно точно с потерей. Для картинки из примера 24-битный цвет, конечно, избыточен — но никто не мешает и в самом ФШ выбрать png8.


      1. Alexufo
        03.04.2016 16:13
        +1

        У photoshop нет возможности сохранять png8 с полутоновой маской как у png24, она у него битовая. А битовая маска прозрачности равносильна тому что прозрачности как таковой нет вообще — она бесполезна. png24 формат lossless — для веба кране неудобный при больших картинках — сами видите 1.3mb при 600px на 600px это капец. PNG panda делает png8 но с полутоновой маской прозрачности.
        Что зачастую КУДА важнее, чем наличие truecolor.


        1. dom1n1k
          03.04.2016 16:24
          +2

          Офигеть, и правда.
          Я всегда пользовался 32 битами + pngout, который переводит картинку в 8 бит, если это не ведет к потере качества, а иначе сохраняет 32.


        1. moldabekov
          05.04.2016 13:49

          Я вот пытаюсь нагуглить PNG panda, может тыкнете куда смотреть?



  1. Psychosynthesis
    03.04.2016 04:24

    А без препроцессоров слабо?


  1. PavlovM
    03.04.2016 07:22
    +3

    Этот урок для начинающих, с начальным знанием JS и CSS
    И тут же: HAML, SCSS, Coffeescript.

    Все таки для начинающих нужно писать на чистом HTML, чистом CSS и чистом JS.
    Тем более, код простой и никакой существенной выгоды от использования вышеперечисленного — нет.
    Хотя, конечно, это скорее претензия к автору исходной статьи.


  1. yea
    03.04.2016 17:44
    +3

    Заново прогонять querySelectorAll при каждом событии скролла — здорово.


  1. PozitivuDA
    03.04.2016 17:45
    -2

    Круто! Возможно, когда-то и до этого руки дойдут.


  1. Tenebrius
    03.04.2016 17:45
    -2

    А обязательно ли использовать JS? Ведь можно сделать параллакс без него.
    Например: habrahabr.ru/post/235531


  1. asv2001
    03.04.2016 17:46

    Я наверное не стал использовать бы translate3d, если есть translateY или просто translate. Насколько я знаю translate3d не очень хорошо работает на iOS 5 (совсем не работает?), но при этом translate и translateY работают хорошо и во всех браузерах.
    З.Ы. Если вы пользуете translate3d для получения ускорения, тоже самое произойдет и при использовании translate и translateY, т.к. transform принуждает браузер включить аппаратное ускорение.
    Спасибо за пример!


    1. Xao
      04.04.2016 09:28
      +1

      Зато translate3d включает аппаратное ускорение, хак такой. Хотя я точно не могу сказать, но слышал что-то подобное.


      1. SerafimArts
        04.04.2016 10:44

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


      1. asv2001
        04.04.2016 13:22

        да, Вы все верно говорите. Я о том же, но апаратное ускорение включает также простое наличие transform. Излишнее использование translateZ может привести к проблемам описанным по ссылке: https://plus.google.com/+NatDuca/posts/BvMgvdnBvaQ?e=-RedirectToSandbox


  1. skipidar
    03.04.2016 19:08

    Спасибо! Мотивирует наконец поиграть с параллаксом


  1. phoenixweiss
    03.04.2016 23:10
    +1

    тутор начального уровня смотрится странно в 2016-м. Уж очень странный материал для перевода выбрали. Кроме того, уже писали что "начинающий" не поймет почему такой "яваскрипт" не работает.
    В общем, тех кто знает про Coffee параллаксом не удивишь, а новичков в тупик явно этим "уроком" поставите. Для остальных, как мне кажется, объем и ценность материала все же не совсем для уровня хабра, скорее для личного блога.


  1. switchON
    04.04.2016 10:45
    +1

    http://www.firewatchgame.com
    Тут этот эффект красиво выглядит.