केस स्टडी - 20thingsilearned.com का पेज फ़्लिप इफ़ेक्ट

परिचय

साल 2010 में, F-i.com और Google Chrome की टीम ने मिलकर, एचटीएमएल5 पर आधारित एक एजुकेशनल वेब ऐप्लिकेशन बनाया था. इसका नाम 20 Things I Learned about Browsers and the Web (www.20thingsilearned.com) था. इस प्रोजेक्ट के पीछे मुख्य आइडिया यह था कि इसे किताब के कॉन्टेक्स्ट में सबसे अच्छे तरीके से पेश किया जा सकता है. किताब का कॉन्टेंट, ओपन वेब टेक्नोलॉजी के बारे में है. इसलिए, हमने कंटेनर को भी ओपन वेब टेक्नोलॉजी का उदाहरण बनाया है. इससे यह पता चलता है कि इन टेक्नोलॉजी की मदद से, आज हम क्या-क्या कर सकते हैं.

'20 Things I Learned About Browsers and the Web' किताब का कवर और होम पेज
"20 Things I Learned About Browsers and the Web" (www.20thingsilearned.com) का बुक कवर और होम पेज

हमने तय किया कि असली दुनिया की किताब जैसा अनुभव देने का सबसे अच्छा तरीका यह है कि ऐनालॉग रीडिंग के अनुभव के अच्छे हिस्सों को सिम्युलेट किया जाए. साथ ही, नेविगेशन जैसे क्षेत्रों में डिजिटल प्लैटफ़ॉर्म के फ़ायदों का इस्तेमाल किया जाए. किताब पढ़ने के अनुभव को बेहतर बनाने के लिए, ग्राफ़िकल और इंटरैक्टिव डिज़ाइन पर काफ़ी मेहनत की गई है. खास तौर पर, किताबों के पेजों को एक से दूसरे पेज पर पलटने के तरीके को बेहतर बनाया गया है.

शुरू करें

इस ट्यूटोरियल में, कैनवस एलिमेंट और JavaScript का इस्तेमाल करके, पेज फ़्लिप करने का इफ़ेक्ट बनाने का तरीका बताया गया है. इस लेख में दिए गए स्निपेट में, कुछ बुनियादी कोड शामिल नहीं किया गया है. जैसे, वैरिएबल के एलान और इवेंट लिसनर की सदस्यता. इसलिए, काम करने वाले उदाहरण का रेफ़रंस देना न भूलें.

शुरू करने से पहले, डेमो देख लें. इससे आपको पता चल जाएगा कि हम क्या बनाने की कोशिश कर रहे हैं.

मार्कअप

यह हमेशा ध्यान रखें कि कैनवस पर बनाई गई इमेज को सर्च इंजन इंडेक्स नहीं कर सकते. साथ ही, न तो कोई व्यक्ति उसे चुन सकता है और न ही ब्राउज़र में की गई खोजों से उसे ढूंढा जा सकता है. इसलिए, जिस कॉन्टेंट पर हम काम करेंगे उसे सीधे तौर पर DOM में रखा जाएगा. इसके बाद, अगर JavaScript उपलब्ध है, तो उसका इस्तेमाल करके कॉन्टेंट में बदलाव किया जाएगा. इसके लिए, कम से कम मार्कअप की ज़रूरत होती है:

<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>

हमारे पास किताब के लिए एक मुख्य कंटेनर एलिमेंट है. इसमें हमारी किताब के अलग-अलग पेज और canvas एलिमेंट शामिल है. हम इसी एलिमेंट पर पेज फ़्लिप करने की सुविधा को लागू करेंगे. section एलिमेंट के अंदर, कॉन्टेंट के लिए div रैपर होता है. हमें इसकी ज़रूरत इसलिए होती है, ताकि हम पेज के कॉन्टेंट के लेआउट पर असर डाले बिना, पेज की चौड़ाई बदल सकें. div की चौड़ाई तय है और section के लिए, ओवरफ़्लो को छिपाने का विकल्प सेट किया गया है. इससे section की चौड़ाई, div के लिए हॉरिज़ॉन्टल मास्क के तौर पर काम करती है.

किताब खोलें.
किताब के एलिमेंट में, पेपर टेक्सचर और ब्राउन बुक जैकेट वाली बैकग्राउंड इमेज जोड़ी गई है.

लॉजिक

पेज फ़्लिप करने की सुविधा के लिए ज़रूरी कोड बहुत जटिल नहीं होता. हालांकि, यह काफ़ी बड़ा होता है, क्योंकि इसमें कई ग्राफ़िक शामिल होते हैं जो अपने-आप जनरेट होते हैं. आइए, सबसे पहले उन कॉन्स्टेंट वैल्यू के बारे में जानते हैं जिनका इस्तेमाल हम पूरे कोड में करेंगे.

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 जोड़ा जाता है, ताकि पन्ने पलटने पर पेपर किताब से बाहर की ओर बढ़ सके. ध्यान दें कि यहां तय किए गए कुछ कॉन्स्टेंट, सीएसएस में भी सेट किए जाते हैं. इसलिए, अगर आपको किताब का साइज़ बदलना है, तो आपको वहां भी वैल्यू अपडेट करनी होंगी.

कॉन्स्टेंट.
इंटरैक्शन को ट्रैक करने और पेज फ़्लिप करने के लिए, पूरे कोड में इस्तेमाल की गई कॉन्स्टेंट वैल्यू.

इसके बाद, हमें हर पेज के लिए एक फ़्लिप ऑब्जेक्ट तय करना होगा. जब हम किताब के साथ इंटरैक्ट करेंगे, तब ये ऑब्जेक्ट लगातार अपडेट होते रहेंगे, ताकि फ़्लिप का मौजूदा स्टेटस दिखे.

// 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
});
}

सबसे पहले, हमें यह पक्का करना होगा कि पेजों को सही तरीके से लेयर किया गया हो. इसके लिए, सेक्शन एलिमेंट के z-इंडेक्स को व्यवस्थित करें, ताकि पहला पेज सबसे ऊपर और आखिरी पेज सबसे नीचे हो. फ़्लिप ऑब्जेक्ट की सबसे ज़रूरी प्रॉपर्टी, progress और target वैल्यू होती हैं. इनका इस्तेमाल यह तय करने के लिए किया जाता है कि पेज को फ़िलहाल कितना फ़ोल्ड किया जाना चाहिए. -1 का मतलब है कि पेज को पूरी तरह से बाईं ओर फ़ोल्ड किया गया है, 0 का मतलब है कि पेज को किताब के बीच में फ़ोल्ड किया गया है, और +1 का मतलब है कि पेज को किताब के सबसे दाईं ओर फ़ोल्ड किया गया है.

प्रोग्रेस.
पेज फ़्लिप करने की प्रोग्रेस और टारगेट वैल्यू का इस्तेमाल यह तय करने के लिए किया जाता है कि फ़ोल्डिंग पेज को -1 से +1 स्केल पर कहां ड्रा किया जाना चाहिए.

अब हमारे पास हर पेज के लिए, फ़्लिप ऑब्जेक्ट तय किया गया है. इसलिए, हमें फ़्लिप की स्थिति को अपडेट करने के लिए, उपयोगकर्ता के इनपुट को कैप्चर करना और उसका इस्तेमाल करना होगा.

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 फ़ंक्शन, mouse ऑब्जेक्ट को अपडेट करता है, ताकि हम हमेशा कर्सर की सबसे नई जगह पर काम कर सकें.

mouseDownHandler में, हम यह देखते हैं कि माउस को बाएं या दाएं पेज पर दबाया गया था या नहीं, ताकि हमें पता चल सके कि हमें किस दिशा में फ़्लिप करना है. हम यह भी पक्का करते हैं कि उस दिशा में कोई दूसरा पेज मौजूद हो, क्योंकि हम पहले या आखिरी पेज पर हो सकते हैं. अगर इन जांचों के बाद, फ़्लिप करने का कोई मान्य विकल्प उपलब्ध होता है, तो हम फ़्लिप किए गए ऑब्जेक्ट के लिए dragging फ़्लैग को true पर सेट कर देते हैं.

mouseUpHandler पर पहुंचने के बाद, हम सभी flips की समीक्षा करते हैं. साथ ही, यह देखते हैं कि क्या इनमें से किसी को dragging के तौर पर फ़्लैग किया गया था और अब उसे रिलीज़ किया जाना चाहिए. जब कोई फ़्लिप रिलीज़ होता है, तो हम उसकी टारगेट वैल्यू को उस साइड से मैच करने के लिए सेट करते हैं जिस पर उसे फ़्लिप करना चाहिए. यह मौजूदा माउस की पोज़िशन पर निर्भर करता है. इस नेविगेशन को दिखाने के लिए, पेज नंबर भी अपडेट किया जाता है.

रेंडरिंग

अब जब हमारा ज़्यादातर लॉजिक तैयार हो गया है, तो हम कैनवस एलिमेंट पर फ़ोल्ड किए गए पेपर को रेंडर करने का तरीका जानेंगे. यह ज़्यादातर render() फ़ंक्शन के अंदर होता है. इसे हर सेकंड में 60 बार कॉल किया जाता है, ताकि सभी ऐक्टिव फ़्लिप की मौजूदा स्थिति को अपडेट और ड्रॉ किया जा सके.

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 को रेंडर करने से पहले, हम clearRect(x,y,w,h) तरीके का इस्तेमाल करके कैनवस को रीसेट करते हैं. पूरे कैनवस को मिटाने से परफ़ॉर्मेंस पर काफ़ी असर पड़ता है. इसलिए, सिर्फ़ उन हिस्सों को मिटाना ज़्यादा बेहतर होगा जिन पर हम ड्रॉ कर रहे हैं. इस ट्यूटोरियल को विषय के हिसाब से रखने के लिए, हम पूरे कैनवस को मिटा देंगे.

अगर किसी फ़्लिप को ड्रैग किया जा रहा है, तो हम उसकी target वैल्यू को अपडेट करते हैं, ताकि वह माउस की पोज़िशन से मेल खाए. हालांकि, यह वैल्यू असल पिक्सल के बजाय -1 से 1 के स्केल पर होती है. हम progress को target से दूरी के कुछ हिस्से से भी बढ़ाते हैं. इससे फ़्लिपिंग की प्रोसेस स्मूद और ऐनिमेशन वाली हो जाएगी, क्योंकि यह हर फ़्रेम पर अपडेट होता है.

हम हर फ़्रेम पर सभी flips की जांच कर रहे हैं. इसलिए, हमें यह पक्का करना होगा कि हम सिर्फ़ उन flips को फिर से बनाएं जो चालू हैं. अगर फ़्लिप, किताब के किनारे के बहुत करीब नहीं है (BOOK_WIDTH के 0.3% के अंदर) या अगर इसे dragging के तौर पर फ़्लैग किया गया है, तो इसे चालू माना जाता है.

अब जब सभी लॉजिक सेट हो गए हैं, तो हमें फ़्लिप की मौजूदा स्थिति के आधार पर उसका ग्राफ़िकल प्रज़ेंटेशन बनाना होगा. अब drawFlip(flip) फ़ंक्शन के पहले हिस्से को देखने का समय है.

// 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';

कोड का यह सेक्शन, कई विज़ुअल वैरिएबल की गिनती करके शुरू होता है. इनकी मदद से, फ़ोल्ड को असल जैसा बनाया जाता है. यहाँ, हम जिस फ़्लिप को ड्रा कर रहे हैं उसकी progress वैल्यू बहुत अहम भूमिका निभाती है, क्योंकि हमें पेज फ़ोल्ड को उसी जगह पर दिखाना है. पेज फ़्लिप करने के इफ़ेक्ट को ज़्यादा बेहतर बनाने के लिए, हम कागज़ को किताब के ऊपर और नीचे के किनारों से बाहर की ओर बढ़ाते हैं. यह इफ़ेक्ट तब सबसे ज़्यादा दिखता है, जब पेज फ़्लिप करने की कार्रवाई किताब के स्पाइन के पास होती है.

सेटिंग बदलें
पेज फ़ोल्ड होने या उसे ड्रैग करने पर, वह इस तरह दिखता है.

अब सभी वैल्यू तैयार हो गई हैं. अब सिर्फ़ पेपर ड्रॉ करना बाकी है!

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();

कैनवस एपीआई के translate(x,y) तरीके का इस्तेमाल, कोऑर्डिनेट सिस्टम को ऑफ़सेट करने के लिए किया जाता है, ताकि हम पेज फ़्लिप को इस तरह से बना सकें कि स्पाइन का ऊपरी हिस्सा 0,0 पोज़िशन के तौर पर काम करे. ध्यान दें कि हमें कैनवस के मौजूदा ट्रांसफ़ॉर्मेशन मैट्रिक्स को save() करने और ड्राइंग पूरी होने के बाद उसे restore() करने की भी ज़रूरत होती है.

अनुवाद करें
यह वह पॉइंट है जहां से हम पेज को फ़्लिप करते हैं. इमेज का ओरिजनल 0,0 पॉइंट, सबसे ऊपर बाईं ओर होता है. हालांकि, इसे translate(x,y) की मदद से बदलकर, हम ड्रॉइंग लॉजिक को आसान बना देते हैं.

हम मुड़े हुए कागज़ की इमेज में foldGradient का इस्तेमाल करेंगे, ताकि उसे असली जैसा दिखाने के लिए हाइलाइट और परछाईं जोड़ी जा सकें. हम पेपर पर बनी ड्राइंग के चारों ओर एक बहुत पतली लाइन भी जोड़ते हैं, ताकि हल्के रंग के बैकग्राउंड पर रखने पर पेपर गायब न हो.

अब हमें सिर्फ़ ऊपर बताई गई प्रॉपर्टी का इस्तेमाल करके, मुड़े हुए कागज़ का आकार बनाना है. हमारे पेपर के बाईं और दाईं ओर सीधी लाइनें खींची गई हैं. साथ ही, ऊपर और नीचे की ओर की लाइनों को घुमाया गया है, ताकि पेपर को मोड़ने का एहसास हो. पेपर के मुड़ने की क्षमता, verticalOutdent वैल्यू से तय होती है.

हो गया! अब आपके पास पूरी तरह से काम करने वाला पेज फ़्लिप नेविगेशन है.

पेज फ़्लिप करने की सुविधा का डेमो

पेज फ़्लिप इफ़ेक्ट से, इंटरैक्टिव फ़ील के बारे में सही जानकारी मिलती है. इसलिए, इसकी इमेज देखने से सही जानकारी नहीं मिलती.

अगले चरण

हार्ड-फ़्लिप
इस ट्यूटोरियल में, पेज को हल्के से फ़्लिप करने की सुविधा को और भी बेहतर बनाया जा सकता है. इसके लिए, किताब जैसी अन्य सुविधाओं का इस्तेमाल करें. जैसे, इंटरैक्टिव हार्ड कवर.

यह सिर्फ़ एक उदाहरण है कि कैनवस एलिमेंट जैसी HTML5 सुविधाओं का इस्तेमाल करके क्या किया जा सकता है. हमारा सुझाव है कि आप इस तकनीक के बारे में ज़्यादा जानने के लिए, इस लिंक पर जाएं: www.20thingsilearned.com. यहां आपको पता चलेगा कि पेज फ़्लिप करने की सुविधा को किसी असली ऐप्लिकेशन में कैसे लागू किया जा सकता है. साथ ही, यह भी पता चलेगा कि इसे HTML5 की अन्य सुविधाओं के साथ इस्तेमाल करने पर, यह कितना असरदार हो जाता है.

रेफ़रंस

  • Canvas API की खास बातें