Örnek Olay - 20thingsilearned.com'dan Sayfa Çevirme Efekti

Hakim El Hattab
Hakim El Hattab

Giriş

2010 yılında F-i.com ve Google Chrome ekibi, "Tarayıcılar ve Web Hakkında Öğrendiğim 20 Şey" adlı HTML5 tabanlı bir eğitim web uygulaması (www.20thingsilearned.com) üzerinde ortak çalışma yürüttüler. Bu projenin temel fikirlerinden biri, sunumun en iyi kitap bağlamında sunulmasıydı. Kitabın içeriği açık web teknolojileriyle ilgili olduğundan, kapsayıcının kendisini bu teknolojilerin bugün başarmamıza olanak tanıdığı şeylere örnek yaparak buna sadık kalmanın önemli olduğunu düşündük.

"Tarayıcılar ve Web Hakkında Öğrendiğim 20 Şey" başlıklı kitabın kitap kapağı ve ana sayfası
"Tarayıcılar ve Web Hakkında Öğrendiğim 20 Şey" adlı kitabın kapağı ve ana sayfası (www.20thingsilearned.com)

Gerçek dünya kitabı hissi yaratmanın en iyi yolunun, navigasyon gibi alanlarda dijital dünyanın avantajlarından yararlanmaya devam ederken analog okuma deneyiminin iyi yanlarını simüle etmek olduğuna karar verdik. Okuma akışının grafik ve etkileşimli olarak işlenmesi, özellikle de kitap sayfalarının bir sayfadan diğerine nasıl dönüştüğü üzerine çok çalışıldı.

Başlarken

Bu eğitim, tuval öğesini ve bol miktarda JavaScript'i kullanarak kendi sayfa çevirme efektinizi oluşturma sürecini adım adım gösterir. Değişken bildirimleri ve etkinlik işleyici aboneliği gibi bazı temel kod, bu makaledeki snippet'lerin dışında bırakılmıştır. Dolayısıyla, çalışan örneğe referans vermeyi unutmayın.

Başlamadan önce demoya göz atıp ne geliştirmeye çalıştığımızı öğrenmenizi öneririz.

Markup

Tuvale çizdiğimiz şeylerin arama motorları tarafından dizine eklenemeyeceğini, bir ziyaretçi tarafından seçilemeyeceğini veya tarayıcıda yapılan aramalarla bulunamayacağını her zaman unutmamak gerekir. Bu nedenle, çalışacağımız içerik doğrudan DOM'nin içine yerleştirilir ve varsa JavaScript ile değiştirilir. Bunun için gereken işaretleme minimum düzeydedir:

<div id='book'>
<canvas id='pageflip-canvas'></canvas>
<div id='pages'>
<section>
    <div> <!-- Any type of contents here --> </div>
</section>
<!-- More <section>s here -->
</div>
</div>

Kitap için bir ana kapsayıcı öğemiz vardır. Bu öğe de aynı şekilde kitabımızın farklı sayfalarını ve çevrilir sayfaları çizeceğimiz canvas öğesini içerir. section öğesinin içinde içerik için bir div sarmalayıcı bulunur. Bu sarmalayıcı, içeriğin düzenini etkilemeden sayfanın genişliğini değiştirebilmemiz için gereklidir. div sabit bir genişliğe sahiptir ve section, taşmalarını gizleyecek şekilde ayarlanmıştır. Bu durum, section genişliğinin div için yatay maske görevi görür.

Kitap&#39;ı açın.
Kitap öğesine, kağıt dokusunu ve kahverengi kitap ceketini içeren bir arka plan resmi eklenir.

Mantık

Sayfa çevirmeyi desteklemek için gereken kod çok karmaşık değildir, ancak prosedürel olarak oluşturulan birçok grafik içerdiğinden oldukça kapsamlıdır. Kodda kullanacağımız sabit değerlerin açıklamasını inceleyerek başlayalım.

var BOOK_WIDTH = 830;
var BOOK_HEIGHT = 260;
var PAGE_WIDTH = 400;
var PAGE_HEIGHT = 250;
var PAGE_Y = ( BOOK_HEIGHT - PAGE_HEIGHT ) / 2;
var CANVAS_PADDING = 60;

CANVAS_PADDING, çevirirken kağıdın kitabın dışına taşabilmesi için tuvalin etrafına eklenir. Burada tanımlanan bazı sabit değerlerin CSS'de de ayarlandığını unutmayın. Bu nedenle, kitabın boyutunu değiştirmek isterseniz buradaki değerleri de güncellemeniz gerekir.

Sabitler.
Etkileşimi izlemek ve sayfanın çevrilmesini sağlamak için kod genelinde kullanılan sabit değerler.

Daha sonra, her sayfa için bir çevirme nesnesi tanımlamamız gerekir; bunlar, kitapla etkileşimimiz sürecin mevcut durumunu yansıtacak şekilde güncellenir.

// Create a reference to the book container element
var book = document.getElementById( 'book' );

// Grab a list of all section elements (pages) within the book
var pages = book.getElementsByTagName( 'section' );

for( var i = 0, len = pages.length; i < len; i++ ) {
pages[i].style.zIndex = len - i;

flips.push( {
progress: 1,
target: 1,
page: pages[i],
dragging: false
});
}

Öncelikle, bölüm öğelerinin Z-endekslerini ilk sayfa üstte ve son sayfa en altta olacak şekilde düzenleyerek sayfaların doğru bir şekilde katmanlandığından emin olmamız gerekir. Döndürme nesnelerinin en önemli özellikleri progress ve target değerleridir. Bunlar, sayfanın şu anda ne kadar katlanacağını belirlemek için kullanılır; -1 tamamen sola, 0 kitabın ölü merkezi, +1 ise kitabın en sağ kenarı anlamına gelir.

İlerleme.
Çevirmelerin ilerleme durumu ve hedef değerleri, katlanan sayfanın -1 ile +1 ölçeğinde çizileceği yeri belirlemek için kullanılır.

Artık her sayfa için tanımlanmış bir çevirme nesnesi olduğuna göre, çevirmenin durumunu güncellemek için kullanıcı girişini yakalamaya ve kullanmaya başlamamız gerekir.

function mouseMoveHandler( event ) {
// Offset mouse position so that the top of the book spine is 0,0
mouse.x = event.clientX - book.offsetLeft - ( BOOK_WIDTH / 2 );
mouse.y = event.clientY - book.offsetTop;
}

function mouseDownHandler( event ) {
// Make sure the mouse pointer is inside of the book
if (Math.abs(mouse.x) < PAGE_WIDTH) {
if (mouse.x < 0 &amp;&amp; page - 1 >= 0) {
    // We are on the left side, drag the previous page
    flips[page - 1].dragging = true;
}
else if (mouse.x > 0 &amp;&amp; page + 1 < flips.length) {
    // We are on the right side, drag the current page
    flips[page].dragging = true;
}
}

// Prevents the text selection
event.preventDefault();
}

function mouseUpHandler( event ) {
for( var i = 0; i < flips.length; i++ ) {
// If this flip was being dragged, animate to its destination
if( flips[i].dragging ) {
    // Figure out which page we should navigate to
    if( mouse.x < 0 ) {
    flips[i].target = -1;
    page = Math.min( page + 1, flips.length );
    }
    else {
    flips[i].target = 1;
    page = Math.max( page - 1, 0 );
    }
}

flips[i].dragging = false;
}
}

mouseMoveHandler işlevi, her zaman en son imleç konumuna doğru çalışmamız için mouse nesnesini günceller.

mouseDownHandler'de farenin sol tarafta mı yoksa sağ sayfada mı basıldığını kontrol ederek başlarız. Böylece, hangi yöne doğru dönmek istediğimizi biliriz. Ayrıca, ilk veya son sayfada olabileceğimiz için bu yönde başka bir sayfanın mevcut olmasını da sağlarız. Bu kontrollerden sonra geçerli bir çevirme seçeneği kullanılabilirse karşılık gelen çevirme nesnesinin dragging işaretini true olarak ayarlarız.

mouseUpHandler kitlesine ulaştıktan sonra tüm flips öğelerini inceleyip bunlardan herhangi birinin dragging olarak işaretlenip işaretlenmediğini ve artık serbest bırakılmış olup olmadığını kontrol ederiz. Bir çevirme serbest kaldığında, geçerli fare konumuna bağlı olarak hedef değerini, çevirmesi gereken tarafla eşleşecek şekilde ayarlarız. Sayfa numarası da bu gezinme tarzını yansıtacak şekilde güncellenir.

Oluşturma

Artık mantığımızın büyük bir kısmı yerinde olduğuna göre, katlanan kağıdın tuval öğesine nasıl oluşturulacağını göreceğiz. Bu işlemlerin çoğu, tüm etkin çevirmelerin güncel durumunu güncellemek ve mevcut durumunu çizmek için saniyede 60 kez çağrılan render() işlevinin içinde gerçekleşir.

function render() {
// Reset all pixels in the canvas
context.clearRect( 0, 0, canvas.width, canvas.height );

for( var i = 0, len = flips.length; i < len; i++ ) {
var flip = flips[i];

if( flip.dragging ) {
    flip.target = Math.max( Math.min( mouse.x / PAGE_WIDTH, 1 ), -1 );
}

// Ease progress towards the target value
flip.progress += ( flip.target - flip.progress ) * 0.2;

// If the flip is being dragged or is somewhere in the middle
// of the book, render it
if( flip.dragging || Math.abs( flip.progress ) < 0.997 ) {
    drawFlip( flip );
}

}
}

flips öğesini oluşturmaya başlamadan önce clearRect(x,y,w,h) yöntemini kullanarak kanvası sıfırladık. Tuvalin tamamının temizlenmesi yüksek performans maliyetine neden olur ve yalnızca faydalandığımız bölgeleri temizlemek çok daha verimli olur. Bu eğiticiyi konudan sapmaması için tüm zemini temizlemeye devam edeceğiz.

Bir kapak sürükleniyorsa target değerini fare konumuyla eşleşecek şekilde güncelleriz ancak gerçek pikseller yerine -1 ila 1 ölçeğinde çalışırız. Ayrıca progress değerini target öğesine olan mesafenin bir kısmına kadar artırırız. Bu, her karede güncellendiğinden çevirmenin sorunsuz ve animasyonlu ilerlemesini sağlar.

Her karede flips öğesinin tamamını incelediğimiz için yalnızca etkin olanları tekrar çizdiğimizden emin olmamız gerekiyor. Kapak, kitap kenarına çok yakın değilse (BOOK_WIDTH alanının% 0, 3'ü kadar mesafede) veya dragging olarak işaretlenmişse etkin olarak kabul edilir.

Artık tüm mantığın uygulandığına göre, bir çevirmenin mevcut durumuna bağlı grafik temsilini çizmemiz gerekir. Şimdi drawFlip(flip) işlevinin ilk bölümüne bakalım.

// Determines the strength of the fold/bend on a 0-1 range
var strength = 1 - Math.abs( flip.progress );

// Width of the folded paper
var foldWidth = ( PAGE_WIDTH * 0.5 ) * ( 1 - flip.progress );

// X position of the folded paper
var foldX = PAGE_WIDTH * flip.progress + foldWidth;

// How far outside of the book the paper is bent due to perspective
var verticalOutdent = 20 * strength;

// The maximum widths of the three shadows used
var paperShadowWidth = (PAGE_WIDTH*0.5) * Math.max(Math.min(1 - flip.progress, 0.5), 0);
var rightShadowWidth = (PAGE_WIDTH*0.5) * Math.max(Math.min(strength, 0.5), 0);
var leftShadowWidth = (PAGE_WIDTH*0.5) * Math.max(Math.min(strength, 0.5), 0);

// Mask the page by setting its width to match the foldX
flip.page.style.width = Math.max(foldX, 0) + 'px';

Kodun bu bölümü, katı gerçekçi bir şekilde çizmemiz için gereken bir dizi görsel değişkeni hesaplayarak başlar. Çizdiğimiz çevirmenin progress değeri, sayfa katlama sayfasının görünmesini istediğimiz için burada büyük bir rol oynar. Sayfa çevirme efektine derinlik eklemek için kağıdın kitabın üst ve alt kenarlarının dışına uzanmasını sağlarız. Bu efekt, kitap sırtına yakın olduğunda en üst düzeye ulaşır.

Çevir
Sayfa çevrildiğinde veya sürüklendiğinde sayfa orta çizgisi böyle görünür.

Tüm değerler hazır olduğuna göre geriye kalan tek şey kağıda çizim yapmaktır.

context.save();
context.translate( CANVAS_PADDING + ( BOOK_WIDTH / 2 ), PAGE_Y + CANVAS_PADDING );

// Draw a sharp shadow on the left side of the page
context.strokeStyle = `rgba(0,0,0,`+(0.05 * strength)+`)`;
context.lineWidth = 30 * strength;
context.beginPath();
context.moveTo(foldX - foldWidth, -verticalOutdent * 0.5);
context.lineTo(foldX - foldWidth, PAGE_HEIGHT + (verticalOutdent * 0.5));
context.stroke();

// Right side drop shadow
var rightShadowGradient = context.createLinearGradient(foldX, 0,
            foldX + rightShadowWidth, 0);
rightShadowGradient.addColorStop(0, `rgba(0,0,0,`+(strength*0.2)+`)`);
rightShadowGradient.addColorStop(0.8, `rgba(0,0,0,0.0)`);

context.fillStyle = rightShadowGradient;
context.beginPath();
context.moveTo(foldX, 0);
context.lineTo(foldX + rightShadowWidth, 0);
context.lineTo(foldX + rightShadowWidth, PAGE_HEIGHT);
context.lineTo(foldX, PAGE_HEIGHT);
context.fill();

// Left side drop shadow
var leftShadowGradient = context.createLinearGradient(
foldX - foldWidth - leftShadowWidth, 0, foldX - foldWidth, 0);
leftShadowGradient.addColorStop(0, `rgba(0,0,0,0.0)`);
leftShadowGradient.addColorStop(1, `rgba(0,0,0,`+(strength*0.15)+`)`);

context.fillStyle = leftShadowGradient;
context.beginPath();
context.moveTo(foldX - foldWidth - leftShadowWidth, 0);
context.lineTo(foldX - foldWidth, 0);
context.lineTo(foldX - foldWidth, PAGE_HEIGHT);
context.lineTo(foldX - foldWidth - leftShadowWidth, PAGE_HEIGHT);
context.fill();

// Gradient applied to the folded paper (highlights &amp; shadows)
var foldGradient = context.createLinearGradient(
foldX - paperShadowWidth, 0, foldX, 0);
foldGradient.addColorStop(0.35, `#fafafa`);
foldGradient.addColorStop(0.73, `#eeeeee`);
foldGradient.addColorStop(0.9, `#fafafa`);
foldGradient.addColorStop(1.0, `#e2e2e2`);

context.fillStyle = foldGradient;
context.strokeStyle = `rgba(0,0,0,0.06)`;
context.lineWidth = 0.5;

// Draw the folded piece of paper
context.beginPath();
context.moveTo(foldX, 0);
context.lineTo(foldX, PAGE_HEIGHT);
context.quadraticCurveTo(foldX, PAGE_HEIGHT + (verticalOutdent * 2),
                        foldX - foldWidth, PAGE_HEIGHT + verticalOutdent);
context.lineTo(foldX - foldWidth, -verticalOutdent);
context.quadraticCurveTo(foldX, -verticalOutdent * 2, foldX, 0);

context.fill();
context.stroke();

context.restore();

Tuval API'sinin translate(x,y) yöntemi, koordinat sistemini dengelemek için kullanılır. Böylece, sayfa çevirmemizi, sırtın üst kısmı 0,0 konumu olarak çalışacak şekilde çizebiliriz. Çizimi tamamladığımızda zeminin mevcut dönüşüm matrisine save() ve restore() buna da eklememiz gerektiğini unutmayın.

Çeviri
Bu noktadan itibaren sayfa çeviriyoruz. Orijinal 0,0 noktası resmin sol üst tarafındadır ancak bunu çeviri(x,y) aracılığıyla değiştirerek çizim mantığını basitleştiririz.

foldGradient, katlanmış kağıdın şeklini gerçekçi vurgular ve gölgeler sağlamak için dolduracağımız öğedir. Ayrıca, kağıt çizimin etrafına çok ince bir çizgi de ekleriz. Böylece, kağıt açık renkli arka planlar üzerine yerleştirildiğinde ortaya çıkmaz.

Şimdi geriye kalan tek şey yukarıda tanımladığımız özellikleri kullanarak katlanmış kağıdın şeklini çizmektir. Kağıdımın sol ve sağ kenarları düz çizgiler olarak çizilir. Üst ve alt kenarları katlanan bir kağıdın bükülmüş hissini verecek şekilde eğimlidir. Bu kağıt kıvrımının gücü verticalOutdent değeriyle belirlenir.

İşte bu kadar. Artık tam işlevli bir sayfa çevirmeye sahipsiniz.

Sayfa Çevirme Demosu

Sayfa çevirme efekti, doğru etkileşimli hissi iletmekle ilgilidir. Bu nedenle bu efektin resimlerine bakmak tam olarak adaletli olmaz.

Sonraki adımlar

Sert çevirme
Bu eğitimdeki yumuşak sayfa çevirme özelliği, etkileşimli ciltli kapak gibi kitaba benzer diğer özelliklerle birlikte kullanıldığında daha da güçlü hale gelir.

Bu, tuval öğesi gibi HTML5 özellikleri kullanılarak neler yapılabileceğine dair yalnızca bir örnektir. Bu tekniğin alıntı niteliğinde olduğu daha ayrıntılı kitap deneyimine göz atmanızı öneririz: www.20thingsilearned.com. Burada, sayfa çevirmelerinin gerçek bir uygulamada nasıl uygulanabileceğini ve diğer HTML5 özellikleriyle birlikte kullanıldığında ne kadar güçlü hale geldiğini göreceksiniz.

Referanslar