Paralaks'

Paul Lewis

Giriş

Yakın zamanda paralaks siteler oldukça popüler oldu. Aşağıdakilere göz atın:

Bu sitelerde, siz ekranı kaydırdıkça sayfanın görsel yapısı değişir. Normalde sayfadaki öğeler, sayfadaki kaydırma konumuna orantılı olarak ölçeklendirilir, döndürülür veya hareket ettirilir.

Paralaks efektli bir demo sayfası
Paralel kayma efekti içeren demo sayfamız

Paralaks siteleri beğenmek veya beğenmemek bir şeydir ancak bu sitelerin performans açısından bir kara delik olduğunu rahatlıkla söyleyebiliriz. Bunun nedeni, tarayıcıların kaydırırken (kaydırma yönünüze bağlı olarak) ekranın üst veya alt kısmında yeni içerik görünmesi için optimize edilmesi ve genel olarak tarayıcıların, kaydırma sırasında görsel olarak çok az değişiklik olduğunda en iyi şekilde çalışmasıdır. Paralaks sitelerde bu durum nadiren yaşanır. Bunun nedeni, sayfanın her yerindeki büyük görsel öğelerin sık sık değişmesi ve tarayıcının sayfanın tamamını yeniden boyamasına neden olmasıdır.

Paralaks siteleri şu şekilde genellenebilir:

  • Yukarı ve aşağı kaydırırken konumlarını, dönüşlerini ve ölçeklerini değiştiren arka plan öğeleri.
  • Metin veya küçük resimler gibi sayfa içeriği, genellikle yukarıdan aşağıya doğru kaydırılır.

Daha önce kaydırma performansı ve uygulamanızın duyarlılığını iyileştirmenin yollarını ele almıştık. Bu makale bu temel üzerine kuruludur. Dolayısıyla, daha önce okumadıysanız bu makaleyi incelemeniz faydalı olabilir.

Soru şu: Paralaks kaydırma siteleri oluştururken pahalı yeniden boyama işlemlerine mahkum musunuz yoksa performansı en üst düzeye çıkarmak için kullanabileceğiniz alternatif yaklaşımlar var mı? Seçeneklerimize göz atalım.

1. seçenek: DOM öğelerini ve mutlak konumları kullanma

Bu, çoğu kullanıcının varsayılan olarak benimsediği yaklaşımdır. Sayfada bir sürü öğe vardır ve bir kaydırma etkinliği tetiklendiğinde bunları dönüştürmek için bir sürü görsel güncelleme yapılır.

DevTools zaman çizelgesini çerçeve modunda başlatır ve ekranı kaydırırsanız pahalı tam ekran boyama işlemlerinin olduğunu fark edersiniz. Çok fazla kaydırırsanız tek bir çerçeve içinde her biri sayfa düzeni çalışmasını tetikleyecek birkaç kaydırma etkinliği görebilirsiniz.

Duraklatılmış kaydırma etkinlikleri olmayan Chrome Geliştirici Araçları.
Tek bir karede büyük boya işlemlerini ve birden fazla etkinlikle tetiklenen düzeni gösteren Geliştirici Araçları.

60 fps'ye (60 Hz'lik tipik monitör yenileme hızıyla eşleşir) ulaşmak için her şeyi 16 ms'den biraz daha kısa sürede tamamlamamız gerektiğini unutmayın. Bu ilk sürümde, her kaydırma etkinliği aldığımızda görsel güncellemelerimizi gerçekleştiriyoruz. Ancak requestAnimationFrame ile daha basit ve etkili animasyonlar ve kaydırma performansı hakkındaki önceki makalelerde de belirttiğimiz gibi, bu durum tarayıcının güncelleme zaman çizelgesiyle örtüşmüyor. Bu nedenle, kareleri kaçırıyoruz veya her birinde çok fazla iş yapıyoruz. Bu durum, sitenizin akıcı ve doğal olmayan bir görünüme sahip olmasına neden olabilir. Bu da hayal kırıklığına uğramış kullanıcılara ve mutsuz kediciklere yol açar.

Güncelleme kodunu kaydırma etkinliğinden bir requestAnimationFrame geri çağırma işlevine taşıyalım ve kaydırma değerini kaydırma etkinliğinin geri çağırma işlevinde yakalayalım.

Kaydırma testini tekrarlarsanız çok olmasa da küçük bir iyileşme fark edebilirsiniz. Bunun nedeni, kaydırarak tetiklediğimiz düzen işleminin çok pahalı olmamasıdır ancak diğer kullanım alanlarında gerçekten pahalı olabilir. Artık her karede en az bir düzen işlemi gerçekleştiriyoruz.

Duraklatılmış kaydırma etkinlikleri içeren Chrome Geliştirici Araçları.
Tek bir karede büyük boya işlemlerini ve birden fazla etkinlikle tetiklenen düzeni gösteren Geliştirici Araçları.

Artık çerçeve başına bir veya yüz kaydırma etkinliğini işleyebiliriz ancak önemli olan, requestAnimationFrame geri çağırma işlevi çalıştırıldığında ve görsel güncellemelerimizi gerçekleştirdiğinde yalnızca en son değeri kullanmak için depolamaktır. Buradaki nokta, her kaydırma etkinliği aldığınızda görsel güncellemeleri zorlamaya çalışmaktan, tarayıcıdan bunları yapacağınız uygun bir pencere vermesini istemeye geçmenizdir. Çok tatlısınız.

requestAnimationFrame olsun veya olmasın bu yaklaşımın temel sorunu, sayfanın tamamı için temelde tek bir katmanımızın olması ve bu görsel öğeleri hareket ettirdiğimizde büyük (ve pahalı) boyama işlemlerine ihtiyaç duymamızdır. Genel olarak boyama, engelleyen bir işlemdir (bu durum değişiyor olsa da). Yani tarayıcı başka hiçbir iş yapamaz ve genellikle çerçevemizin 16 ms'lik bütçesinin çok üzerinde çalışırız. Bu da her şeyin aksak kalmasına neden olur.

2. Seçenek: DOM öğelerini ve 3D dönüştürme işlemlerini kullanma

Mutlak konumlar kullanmak yerine öğelere 3D dönüşümler uygulamak da kullanabileceğimiz bir yaklaşımdır. Bu durumda, 3D dönüşümlerin uygulandığı öğelere öğe başına yeni bir katman verildiğini ve WebKit tarayıcılarında genellikle donanım derleyiciye geçişe de neden olduğunu görüyoruz. Buna karşılık 1. seçenekte, sayfa için tek bir büyük katman vardı. Herhangi bir şey değiştiğinde bu katmanın yeniden boyanması gerekiyordu ve tüm boyama ve kompozisyon işlemleri CPU tarafından gerçekleştiriliyordu.

Yani bu seçenekte işler farklıdır: 3D dönüştürme uyguladığımız her öğe için muhtemelen bir katmanımız olur. Bu noktadan itibaren tek yaptığımız öğelerde daha fazla dönüşüm yapmaksa katmanı yeniden boyamamız gerekmez ve GPU, öğeleri taşıma ve nihai sayfayı bir araya getirme işlemlerini yapabilir.

Çoğu zaman kullanıcılar -webkit-transform: translateZ(0); hilelerini kullanarak performansta sihirli iyileştirmeler elde eder. Bu yöntem şu anda işe yarasa da bazı sorunlar vardır:

  1. Tarayıcılar arası uyumlu değildir.
  2. Dönüştürülen her öğe için yeni bir katman oluşturarak tarayıcıyı zorlar. Çok fazla katman, performansta başka tıkanıklıklara neden olabilir. Bu nedenle, katmanları dikkatli kullanın.
  3. Bazı WebKit bağlantı noktaları için devre dışı bırakıldı (aşağıdan dördüncü madde).

3D çeviri yolunu tercih ederseniz dikkatli olun. Bu, sorununuzun geçici bir çözümüdür. İdeal olarak, 2D dönüştürmelerde 3D'de gördüğümüz benzer oluşturma özelliklerini görürüz. Tarayıcılar olağanüstü bir hızla gelişiyor. Bu nedenle, bu tarihten önce bu özelliği göreceğimizi umuyoruz.

Son olarak, mümkün olduğunda boyalardan kaçınmayı ve mevcut öğeleri sayfa üzerinde taşımayı hedeflemeniz gerekir. Örneğin, paralaks sitelerde sabit yükseklikteki div'leri kullanmak ve efekti sağlamak için arka plan konumlarını değiştirmek tipik bir yaklaşımdır. Maalesef bu, öğenin her geçişte yeniden boyanması gerektiği anlamına gelir. Bu da performans açısından size maliyete neden olabilir. Bunun yerine, mümkünse öğeyi oluşturmalı (gerekirse overflow: hidden ile bir div içine sarmalayın) ve çevirmeniz gerekir.

3. seçenek: Sabit konumlu bir kanvas veya WebGL kullanma

İnceleyeceğimiz son seçenek, sayfanın arkasında sabit konumlu bir kanvas kullanmaktır. Bu kanvasın içine dönüştürülmüş resimlerimizi çizeceğiz. Bu, ilk bakışta en iyi performans gösteren çözüm gibi görünmeyebilir ancak bu yaklaşımın birkaç avantajı vardır:

  • Artık tek bir öğe (tuval) bulunduğundan, birleştirici için daha az çalışma gerekiyor.
  • Tek bir donanım hızlandırmalı bitmap ile çalışıyoruz.
  • Canvas2D API, gerçekleştirmek istediğimiz dönüşüm türleri için mükemmel bir seçimdir. Bu sayede geliştirme ve bakım daha kolay yönetilebilir.

Bir kanvas öğesi kullandığımızda yeni bir katman elde ederiz ancak bu yalnızca bir katmandır. 2. seçenekte ise 3D dönüşüm uygulanmış her öğe için yeni bir katman elde ederiz. Bu nedenle, tüm bu katmanları bir araya getirmek için daha fazla iş yükü üstlenmiş oluruz. Ayrıca, dönüştürmelerin tarayıcılar arası farklı uygulamaları göz önüne alındığında, bu yöntem günümüzde en uyumlu çözümdür.


/**
 * Updates and draws in the underlying visual elements to the canvas.
 */
function updateElements () {

  var relativeY = lastScrollY / h;

  // Fill the canvas up
  context.fillStyle = "#1e2124";
  context.fillRect(0, 0, canvas.width, canvas.height);

  // Draw the background
  context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));

  // Draw each of the blobs in turn
  context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
  context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
  context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
  context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
  context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
  context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
  context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
  context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
  context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));

  // Allow another rAF call to be scheduled
  ticking = false;
}

/**
 * Calculates a relative disposition given the page's scroll
 * range normalized from 0 to 1
 * @param {number} base The starting value.
 * @param {number} range The amount of pixels it can move.
 * @param {number} relY The normalized scroll value.
 * @param {number} offset A base normalized value from which to start the scroll behavior.
 * @returns {number} The updated position value.
 */
function pos(base, range, relY, offset) {
  return base + limit(0, 1, relY - offset) * range;
}

/**
 * Clamps a number to a range.
 * @param {number} min The minimum value.
 * @param {number} max The maximum value.
 * @param {number} value The value to limit.
 * @returns {number} The clamped value.
 */
function limit(min, max, value) {
  return Math.max(min, Math.min(max, value));
}

Bu yaklaşım, büyük resimlerle (veya kanvas üzerine kolayca yazılabilen diğer öğelerle) çalıştığınız durumlarda gerçekten işe yarar. Büyük metin bloklarıyla çalışmak elbette daha zordur ancak sitenize bağlı olarak en uygun çözüm olabilir. Tuvalde metinle uğraşmanız gerekiyorsa fillText API yöntemini kullanmanız gerekir. Ancak bu yöntem erişilebilirlik açısından dezavantajlıdır (metni bir bitmap olarak rasterleştirirsiniz). Ayrıca satır sarma ve bir sürü başka sorunla uğraşmanız gerekir. Bunu yapmamak için elinizden geleni yapmalısınız. Yukarıdaki dönüştürme yaklaşımını kullanmak size daha iyi sonuçlar verecektir.

Bu konuyu mümkün olduğunca ayrıntılı bir şekilde ele aldığımızdan, paralaks çalışmasının bir kanvas öğesi içinde yapılması gerektiğini varsaymaya gerek yoktur. Tarayıcı desteklerse WebGL'yi kullanabiliriz. Buradaki anahtar nokta, WebGL'in grafik kartına giden tüm API'ler arasında en doğrudan rotaya sahip olmasıdır. Bu nedenle, özellikle sitenin efektleri karmaşıksa 60 fps'ye ulaşmak için en uygun aday WebGL'dir.

İlk tepkiniz WebGL'in aşırı olduğunu veya destek açısından her yerde bulunmadığını düşünebilirsiniz. Ancak Three.js gibi bir şey kullanıyorsanız dilediğiniz zaman kanvas öğesi kullanmaya geri dönebilirsiniz. Bu durumda kodunuz tutarlı ve kullanıcı dostu bir şekilde soyutlanır. Uygun API desteğini kontrol etmek için tek yapmamız gereken Modernizr'i kullanmaktır:

// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
  renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
  renderer = new THREE.CanvasRenderer();
}

Bu yaklaşımla ilgili son bir not: Sayfaya fazladan öğe eklemeyi sevmezseniz hem Firefox hem de WebKit tabanlı tarayıcılarda dilediğiniz zaman arka plan öğesi olarak kanvas kullanabilirsiniz. Bu durum her yerde geçerli değildir. Bu nedenle, her zaman olduğu gibi dikkatli davranmanız gerekir.

Seçim size kalmış

Geliştiricilerin varsayılan olarak diğer seçeneklerden ziyade mutlak konumlandırılmış öğeleri tercih etmesinin başlıca nedeni, bu öğelerin her yerde desteklenmesi olabilir. Hedeflenen eski tarayıcıların büyük olasılıkla son derece kötü bir oluşturma deneyimi sunması nedeniyle bu durum bir dereceye kadar yanıltıcıdır. Günümüzde kullanılan modern tarayıcılarda bile, mutlak konumlandırılmış öğelerin kullanılması her zaman iyi performansla sonuçlanmaz.

Dönüşümler (özellikle 3D dönüşümler), doğrudan DOM öğeleriyle çalışmanıza ve sabit bir kare hızına ulaşmanıza olanak tanır. Burada başarılı olmanın anahtarı, mümkün olduğunca boyamaktan kaçınmak ve öğeleri hareket ettirmeye çalışmaktır. WebKit tarayıcıların katman oluşturma şeklinin diğer tarayıcı motorlarıyla mutlaka aynı olmadığını unutmayın. Bu nedenle, bu çözümü kullanmaya karar vermeden önce bunu test ettiğinizden emin olun.

Yalnızca en üst düzey tarayıcıları hedefliyorsanız ve siteyi tuvalleri kullanarak oluşturabiliyorsanız bu sizin için en iyi seçenek olabilir. Three.js kullanıyorsanız ihtiyaç duyduğunuz desteğe bağlı olarak kolayca farklı oluşturma araçları arasında geçiş yapabilirsiniz.

Sonuç

Paralaks sitelerle ilgili olarak, kesinlikle konumlandırılmış öğelerden sabit konumlu kanvas kullanmaya kadar çeşitli yaklaşımları değerlendirdik. Elbette, neyi amaçladığınıza ve üzerinde çalıştığınız tasarıma bağlı olarak kullanacağınız uygulama değişecektir. Ancak seçeneklerinizin olduğunu bilmek her zaman iyidir.

Her zaman olduğu gibi, hangi yaklaşımı denerseniz deneyin tahmin etmeyin, test edin.