Використання SVG clip-path для зміни кольору логотипу

6 хв. читання

Минулого тижня я закінчив сайт для запуску нового бізнесу. Дизайн був досить простим: одна сторінка з різними розділами, представленими горизонтальними блоками в повну ширину з різним кольором фону і з логотипом фіксованим у верхньому лівому куті.

Єдина особливість полягала у тому, що логотип повинен змінювати кольори, під час прокручування сторінку користувачем, в залежності від того, який розділ він перекриває. При розміщенні над секцією з темним фоном, логотип повинен бути білим. А при перекритті розділу зі світлим фоном, він повинен бути темно-синього кольору.

Використання SVG clip-path для зміни кольору логотипу

Першим, що спало на думку, було використати два малюнки й замінювати їх, тоді, коли користувач прокручує сторінку, але тут такий підхід не працює. Погляньмо уважніше на дизайн сторінки вище. Другий екран показує, що логотип перекриває секцію, яка є частково темною і світлою. Коли це відбувається, частини логотипу, які покривають кожну з секцій повинні бути забарвлені відповідно.

Спроба #1: анімація позиціювання

Після деяких пошуків, я натрапив на цей приклад. Він працює, маючи основну версію логотипу, з position: fixed, і кілька копій – по одній для кожного розділу – з position: absolute. Під час прокручування, позиція кожної з копій змінюється таким чином, що ідеально перекриває основний логотип і створює ілюзію того, що це один і той самий елемент.

Під час тестів, усе чудово працювало у Chrome, але зображення мерехтіло у Safari.

Використання SVG clip-path для зміни кольору логотипу
Анімація позиціювання мерехтить у Safari

Спроба #2: SVG clip-path

Коли спроба #1 не вдалась, я намагався підійти до справи з іншого боку. Замість того, щоб створювати різні копії елементу для різних секцій і анімувати їхні позиції, я створив дві копії (одну темну й одну світлу) логотипу і розмістив їх точно одну над одною з фіксованою позицією для кожної.

Далі, потрібно було знайти спосіб приховувати частину верхнього логотипа, таким чином, щоб утворена пустота відкривала відповідну частину нижнього. Це було схожим на маску, тому використати clip-path було хорошим рішенням. При використанні його на елементах HTML, його підтримка обмежена, але мій логотип був у SVG форматі, у цьому випадку, підтримка розповсюджена.

Створення масок

Я почав зі створення SVG <clip-path> елементів для кожного логотипу.

<!-- Основний логотип -->
<svg>
  <defs>
    <clipPath id="logo-main-mask">
      <rect x="0" y="0" width="200" height=[](http://)"120" />
    </clipPath>
  </defs>

  <g clip-path="url(#logo-main-mask)">          
    <use xlink:href="#logo"/>
  </g>
</svg>

<!-- Копія логотипу -->
<svg>
  <defs>
    <clipPath id="logo-alt-mask">
      <rect x="0" y="120" width="200" height="0" />
    </clipPath>
  </defs>          

  <g clip-path="url(#logo-alt-mask)">
    <use xlink:href="#logo"/>
  </g>
</svg>

Тоді я створив функцію, яка дозволяла мені маніпулювати розмірами та розташуванням масок таким чином, що була б відображена точна частина кожного логотипу. Ця функція отримує два аргументи:

  • amount – кількість пікселів верхнього логотипа, які потрібно відобразити
  • isDark – булівське значення, що визначає чи перша секція, яку перекриває логотип, має темний фон
// `logoDimensions` об'єкт, який містить розміри
// основного логотипу
function blendLogoMasks(amount, isDark) {
  if ((maskCache.isDark === isDark) && (maskCache.amount === amount)) {
    return
  }

  var alt = {}
  var main = {}

  if (isDark) {
    alt.y = amount + 1
    alt.height = logoDimensions.height - amount

    main.y = 0
    main.height = amount
  } else {
    alt.y = 0
    alt.height = amount

    main.y = amount + 1
    main.height = logoDimensions.height - amount
  }

  $('#logo-alt-mask rect').attr({
    y: alt.y,
    height: alt.height
  })

  $('#logo-main-mask rect').attr({
    y: main.y,
    height: main.height
  })

  maskCache.isDark = isDark
  maskCache.amount = amount
}

// Це згенерує логотип з 100px білого кольору
// і рештою темного кольору
blendLogoMasks(100, false)

Декілька речей для замітки:

  • У прикладах використано jQuery, для більш лаконічного відображення маніпуляцій з DOM, але це абсолютно не суттєво для створення такої функціональності;
  • maskCache – об'єкт, який зберігає два останніх аргументи (amount та isDark), з якими була викликана функція уникаючи звернення до DOM під час багаторазових викликів функції з тими самими значеннями. Це оптимізація продуктивності, яка далі буде більш зрозумілою.

Відображення розділів

Щоб більш легко розрахувати, коли логотип перекриває кожен розділ, я позначив кожну секцію як об'єкт, що містить: позицію, в якій закінчується секція, наявність світлого чи темного фону та посилання на DOM вузол.

function generateSectionsMap() {
  var sections = []

  $sections.each(function () {
    var top = $(this).offset().top

    sections.push({
      $el: this,
      end: top + $(this).outerHeight(),
      isDark: $(this).hasClass('js-section-dark')
    })
  })

  return sections
}

Оновлення суміші масок під час прокручування

Нарешті, нам потрібно оновити суміш масок, коли користувач прокручує сторінку. Для цього я спочатку створив функцію, яка визначає, коли логотип перекриває один або два розділи, а також кольори їхніх фонів.

function updateLogo(sections) {
  var scrollOffset = $body.scrollTop()
  var logoStart = $logoMain.offset().top
  var logoEnd = logoStart + logoDimensions.height
  var section

  $.each(sections, function (index, section) {
    if (section.end >= logoStart) {
      if (
        section.end <= logoEnd &&
        sections[index + 1] &&
        sections[index + 1].isDark !== section.isDark
      ) {
				
        // Логотип знаходиться на межі розділів, перший елемент
        // суміші масок отримує ту кількість пікселів розділу, 
        // яка не знаходиться під логотипом. Ми це робимо тільки тоді, overlapping the logo. 
        // коли розділи мають різний фон, інакше приймаємо це як один розділ.
        blendLogoMasks(section.isDark, section.end - logoStart)
      } else {
        // Логотип - на одному розділі, перший елемент у суміші масок приймає всю висоту логотипу. 
        blendLogoMasks(section.isDark, logoDimensions.height)
      }

      return false
    }
  })
}

У кінці, нам просто потрібно прикріпити цю функцію до події onScroll, а також викликати її, коли сторінка завантажується.

var sections = generateSectionsMap()

updateLogo(sections)

$(window).on('scroll', function () {
  updateLogo(sections)
})

І це працює!

Кінцевий результат

Ось як це виглядає на тій самій версії Safari, яка раніше була проблематичною.

Використання SVG clip-path для зміни кольору логотипу
Результат анімації лого з використанням SVG clip-path

Поглянути й проаналізувати приклад можна тут.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 639
Приєднався: 1 рік тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід