Performanslı Paralaks

Paralaks efekti, beğenin ya da beğenmeyin, kalıcı bir trend. Dikkatli kullanıldığında bir web uygulamasına derinlik ve incelik katabilir. Ancak sorun, paralaks efektini performanslı bir şekilde uygulamanın zor olabilmesidir. Bu makalede, hem yüksek performanslı hem de en az onun kadar önemli olan, tarayıcılar arası çalışan bir çözümü ele alacağız.

Paralaks illüstrasyonu.

TL;DR

  • Paralaks animasyonları oluşturmak için kaydırma etkinliklerini veya background-position kullanmayın.
  • Daha doğru bir paralaks efekti oluşturmak için CSS 3D dönüşümlerini kullanın.
  • Paralaks efektinin yayılmasını sağlamak için Mobil Safari'de position: sticky kullanın.

Hazır çözümü kullanmak istiyorsanız UI Element Samples GitHub deposuna gidip Paralaks yardımcı JS'sini alın. Paralaks kaydırma aracının canlı demosunu GitHub deposunda görebilirsiniz.

Paralaks sorunları

Başlangıç olarak, paralaks efekti elde etmenin iki yaygın yoluna ve özellikle neden amaçlarımız için uygun olmadıklarına göz atalım.

Kötü: Kaydırma etkinliklerini kullanma

Paralaks efektinin temel şartı, kaydırmaya bağlı olmasıdır. Sayfanın kaydırma konumundaki her değişiklikte, paralaks öğesinin konumu güncellenmelidir. Bu basit bir işlem gibi görünse de modern tarayıcıların önemli bir mekanizması, eşzamansız olarak çalışabilmeleridir. Bu durum, özellikle kaydırma etkinlikleri için geçerlidir. Çoğu tarayıcıda kaydırma etkinlikleri "en iyi çaba" ile sunulur ve kaydırma animasyonunun her karesinde sunulacağı garanti edilmez.

Bu önemli bilgi, öğeleri kaydırma etkinliklerine göre hareket ettiren JavaScript tabanlı bir çözümden neden kaçınmamız gerektiğini açıklıyor: JavaScript, paralaksın sayfanın kaydırma konumuyla uyumlu kalacağını garanti etmez. Mobile Safari'nin eski sürümlerinde kaydırma etkinlikleri aslında kaydırmanın sonunda teslim ediliyordu. Bu da JavaScript tabanlı bir kaydırma efekti oluşturmayı imkansız hale getiriyordu. Daha yeni sürümler, animasyon sırasında kaydırma etkinlikleri sağlar ancak Chrome'a benzer şekilde "en iyi çaba" temelinde çalışır. Ana iş parçacığı başka bir işle meşgulse kaydırma etkinlikleri hemen teslim edilmez. Bu da paralaks efektinin kaybolmasına neden olur.

Kötü: background-position güncelleniyor

Kaçınmak istediğimiz bir diğer durum da her kareyi boyamaktır. Birçok çözüm, background-position öğesini değiştirerek paralaks görünümü sağlamaya çalışır. Bu durum, tarayıcının kaydırma sırasında sayfanın etkilenen kısımlarını yeniden çizmesine neden olur ve bu işlem, animasyonu önemli ölçüde sarsacak kadar maliyetli olabilir.

Paralaks hareket vaadini yerine getirmek istiyorsak hızlandırılmış bir özellik olarak uygulanabilen (bu da bugün dönüşümleri ve opaklığı kullanmak anlamına gelir) ve kaydırma etkinliklerine dayanmayan bir şey istiyoruz.

3D CSS

Hem Scott Kellum hem de Keith Clark, CSS 3D'yi kullanarak paralaks hareketi elde etme konusunda önemli çalışmalar yaptı. Kullandıkları teknik şu şekilde özetlenebilir:

  • overflow-y: scroll (ve muhtemelen overflow-x: hidden) ile kaydırılacak bir kapsayıcı öğe ayarlayın.
  • Aynı öğeye perspective değeri ve perspective-origin top left veya 0 0 olarak ayarlanmış bir değer uygulayın.
  • Bu öğenin alt öğelerine Z ekseninde çeviri uygular ve ekrandaki boyutlarını etkilemeden paralaks hareketi sağlamak için bunları tekrar ölçeklendirir.

Bu yaklaşımla ilgili CSS şu şekilde görünür:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

Bu, aşağıdaki gibi bir HTML snippet'i olduğunu varsayar:

<div class="container">
    <div class="parallax-child"></div>
</div>

Perspektif için ölçeği ayarlama

Alt öğeyi geriye itmek, perspektif değeriyle orantılı olarak küçülmesine neden olur. Ne kadar büyütülmesi gerektiğini (perspektif - mesafe) / perspektif denklemiyle hesaplayabilirsiniz. Paralaks öğesinin paralaks oluşturmasını ancak oluşturduğumuz boyutta görünmesini istediğimiz için, olduğu gibi bırakmak yerine bu şekilde ölçeklendirmemiz gerekir.

Yukarıdaki kod örneğinde perspektif 1 piksel, parallax-child öğesinin Z mesafesi ise -2 pikseldir. Bu, öğenin 3 kat büyütülmesi gerektiği anlamına gelir. Bu değeri koda eklenmiş olarak görebilirsiniz: scale(3).

translateZ değeri uygulanmamış tüm içerikler için sıfır değerini kullanabilirsiniz. Bu durumda ölçek (perspective - 0) / perspective olur. Bu da 1 değerine karşılık gelir. Yani ölçek ne büyütülmüş ne de küçültülmüştür. Gerçekten çok kullanışlı.

Bu yaklaşımın işleyiş şekli

Bu yöntemin neden işe yaradığını anlamak önemlidir. Çünkü bu bilgiyi kısa süre sonra kullanacağız. Kaydırma aslında bir dönüşümdür. Bu nedenle hızlandırılabilir. Kaydırma işlemi, katmanların GPU ile taşınmasını içerir. Perspektif kavramı olmayan normal bir kaydırmada, kaydırma öğesi ve alt öğeleri karşılaştırıldığında kaydırma 1:1 oranında gerçekleşir. Bir öğeyi 300px kadar aşağı kaydırırsanız alt öğeleri aynı miktarda (300px) yukarı doğru dönüştürülür.

Ancak kaydırma öğesine bir perspektif değeri uygulamak bu süreci karıştırır. Kaydırma dönüşümünü destekleyen matrisleri değiştirir. Artık 300 piksel kaydırma, seçtiğiniz perspective ve translateZ değerlerine bağlı olarak alt öğeleri yalnızca 150 piksel hareket ettirebilir. Bir öğenin translateZ değeri 0 ise 1:1 oranında kaydırılır (eskiden olduğu gibi). Ancak perspektif başlangıcından Z yönünde itilen bir alt öğe farklı bir hızda kaydırılır. Net sonuç: paralaks hareketi. En önemlisi de bu işlemin, tarayıcının dahili kaydırma mekanizmasının bir parçası olarak otomatik şekilde yapılmasıdır. Bu nedenle, scroll etkinliklerini dinlemeniz veya background-position değerini değiştirmeniz gerekmez.

Küçük bir sorun: Mobil Safari

Her efektin sınırlamaları vardır. Dönüşümlerle ilgili önemli bir sınırlama, alt öğelerde 3D efektlerin korunmasıyla ilgilidir. Perspektif içeren öğe ile paralaks alt öğeleri arasında hiyerarşide öğeler varsa 3D perspektif "düzleştirilir", yani efekt kaybolur.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

Yukarıdaki HTML'de .parallax-container yeni bir öğe olup perspective değerini etkili bir şekilde düzleştirir ve paralaks efektini kaybederiz. Çözüm, çoğu durumda oldukça basittir: Öğeye transform-style: preserve-3d ekleyerek ağacın daha yukarısında uygulanmış olan tüm 3D efektlerin (ör. perspektif değerimiz) yayılmasını sağlarsınız.

.parallax-container {
  transform-style: preserve-3d;
}

Ancak Mobile Safari söz konusu olduğunda işler biraz daha karmaşık bir hal alır. Kapsayıcı öğeye overflow-y: scroll uygulamak teknik olarak işe yarar ancak kaydırma öğesini kaydırma özelliğini kullanma maliyetiyle birlikte gelir. Çözüm, -webkit-overflow-scrolling: touch eklemektir ancak bu işlem perspective öğesini de düzleştirir ve paralaks efekti elde edemeyiz.

Aşamalı geliştirme açısından bu durum muhtemelen çok büyük bir sorun değildir. Her durumda paralaks yapamıyorsak uygulamamız yine de çalışır ancak geçici bir çözüm bulmak iyi olur.

position: sticky imdada yetişti!

Aslında, öğelerin kaydırma sırasında görüntü alanının veya belirli bir üst öğenin üst kısmına "yapışmasına" olanak tanıyan position: sticky biçiminde bir yardım vardır. Çoğu gibi bu spesifikasyon da oldukça kapsamlıdır ancak içinde faydalı bir bilgi yer alır:

Bu ifade ilk bakışta çok önemli görünmeyebilir ancak bu cümledeki önemli nokta, bir öğenin yapışkanlığının tam olarak nasıl hesaplandığına atıfta bulunmasıdır: "Kaydırma kutusu olan en yakın üst öğeye göre ofset hesaplanır". Başka bir deyişle, yapışkan öğenin taşınacağı mesafe (başka bir öğeye veya görüntü alanına bağlı görünmesi için) diğer dönüşümler uygulanmadan önce hesaplanır, sonra değil. Bu, daha önce verilen kaydırma örneğine çok benzer şekilde, ofset 300 piksel olarak hesaplandıysa bu 300 piksel ofset değerini herhangi bir sabit öğeye uygulanmadan önce değiştirmek için perspektifleri (veya başka bir dönüştürme) kullanma fırsatı doğduğu anlamına gelir.

Paralaks öğesine position: -webkit-sticky uygulayarak -webkit-overflow-scrolling: touch'nin düzleştirme etkisini etkili bir şekilde "tersine" çevirebiliriz. Bu, paralaks öğesinin, kaydırma kutusu olan en yakın üst öğeye (bu örnekte .container) referans vermesini sağlar. Ardından, daha önce olduğu gibi .parallax-container, perspective değerini uygular. Bu değer, hesaplanan kaydırma uzaklığını değiştirir ve paralaks efekti oluşturur.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

Bu sayede, Mobile Safari'de paralaks efekti geri yüklenir. Bu da herkes için harika bir haberdir.

Yapışkan konumlandırmayla ilgili uyarılar

Ancak burada bir fark vardır: position: sticky, paralaks mekanizmasını değiştirir. Sabit konumlandırma, öğeyi kaydırma kapsayıcısına yapıştırmaya çalışır. Sabit olmayan konumlandırma ise bunu yapmaz. Bu, sabit uçlu paralaksın, sabit uçlu olmayan paralaksın tersi olduğu anlamına gelir:

  • With position: sticky ile öğe z=0'a ne kadar yakınsa o kadar az hareket eder.
  • position: sticky olmadan, öğe z=0'a ne kadar yakınsa o kadar çok hareket eder.

Tüm bunlar biraz soyut görünüyorsa Robert Flack'in bu demosuna göz atın. Bu demoda, öğelerin sabit konumlandırma ile ve sabit konumlandırma olmadan nasıl farklı davrandığı gösteriliyor. Farkı görmek için Chrome Canary (bu yazı yazıldığı sırada 56. sürüm) veya Safari'yi kullanmanız gerekir.

Paralaks perspektifli ekran görüntüsü

Robert Flack'in, position: sticky'nin paralaks kaydırmayı nasıl etkilediğini gösteren bir demosu.

Çeşitli hatalar ve geçici çözümler

Ancak her şeyde olduğu gibi, düzeltilmesi gereken bazı sorunlar var:

  • Yapışkan destek tutarsızdır. Chrome'da destek henüz uygulanmaktadır, Edge'de ise destek tamamen eksiktir. Firefox'ta yapışkan öğeler perspektif dönüşümleriyle birleştirildiğinde boyama hataları oluşur. Bu gibi durumlarda, yalnızca gerektiğinde position: sticky (-webkit- ön ekli sürüm) eklemek için biraz kod eklemek faydalı olabilir. Bu yalnızca Mobile Safari için geçerlidir.
  • Efekt, Edge'de "sadece çalışmaz". Edge, kaydırma işlemini genellikle iyi bir şey olan işletim sistemi düzeyinde yapmaya çalışır ancak bu durumda kaydırma sırasında perspektif değişikliklerinin algılanmasını engeller. Bunu düzeltmek için sabit konumlu bir öğe ekleyebilirsiniz. Bu, Edge'i işletim sistemi dışı bir kaydırma yöntemine geçirir ve perspektif değişikliklerini hesaba katmasını sağlar.
  • "Sayfanın içeriği çok büyüdü!" Birçok tarayıcı, sayfa içeriğinin ne kadar büyük olduğuna karar verirken ölçeği hesaba katar ancak maalesef Chrome ve Safari perspektifi hesaba katmaz. Dolayısıyla, bir öğeye 3 kat ölçek uygulanırsa perspective uygulandıktan sonra öğe 1 kat olsa bile kaydırma çubukları ve benzeri öğeler görebilirsiniz. Bu sorunu, öğeleri sağ alt köşeden (transform-origin: bottom right ile) ölçeklendirerek çözebilirsiniz. Bu yöntem, aşırı büyük öğelerin kaydırılabilir alanın "negatif bölgesine" (genellikle sol üst) büyümesine neden olacağı için işe yarar. Kaydırılabilir bölgeler, negatif bölgedeki içeriği görmenize veya bu bölgeye kaydırmanıza asla izin vermez.

Sonuç

Paralaks efekti, dikkatli kullanıldığında eğlenceli bir efekt olabilir. Gördüğünüz gibi, bu özelliği performanslı, kaydırmaya bağlı ve tarayıcılar arası olacak şekilde uygulamak mümkündür. İstenen efekti elde etmek için biraz matematiksel işlem ve az miktarda standart metin gerektiğinden, UI Element Samples GitHub deposunda bulabileceğiniz küçük bir yardımcı kitaplık ve örnek oluşturduk.

Oyun oynayın ve nasıl gittiğini bize bildirin.