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

शुरुआती जानकारी

साल 2010 में, F-i.com और Google Chrome की टीम ने HTML5 पर आधारित शिक्षा से जुड़े वेब ऐप्लिकेशन पर साथ मिलकर काम किया. इस वेब ऐप्लिकेशन का नाम 'ब्राउज़र और वेब के बारे में '20 चीज़ें मैंने सीखीं' पर आधारित है (www.20thingsilearned.com). इस प्रोजेक्ट को देने के पीछे एक मुख्य आइडिया यह था कि इसे किसी किताब के संदर्भ में सबसे सही तरीके से दिखाया जाएगा. किताब का कॉन्टेंट, ओपन वेब टेक्नोलॉजी के बारे में काफ़ी हद तक बताता है. हमें लगा कि इस बात पर भरोसा बनाए रखना बहुत ज़रूरी है. इसलिए, कंटेनर को इस बात का उदाहरण बनाया जा सकता है कि आज के दौर में इन टेक्नोलॉजी को क्या हासिल किया जा सकता है.

'ब्राउज़र और वेब के बारे में मेरे द्वारा सीखी गई 20 बातें' का बुक कवर और होम पेज
"20 Things I Learned about Browsers and the Web" (www.20thingsilearned.com) का बुक कवर और होम पेज

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

शुरू करें

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

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

मार्कअप

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

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

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

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

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

पेज फ़्लिप करने का डेमो

पेज फ़्लिप करने की सुविधा की मदद से, लोगों को सही इंटरैक्टिव माहौल के बारे में बताया जाता है. इसलिए, पेज की इमेज देखने से कोई फ़र्क़ नहीं पड़ता.

अगले चरण

हार्ड-फ़्लिप
इस ट्यूटोरियल में दिया गया सॉफ़्ट पेज फ़्लिप और भी ज़्यादा कारगर हो जाता है. ऐसा तब होता है, जब उसे किताब जैसी दूसरी सुविधाओं, जैसे कि इंटरैक्टिव हार्ड कवर सुविधा के साथ जोड़ा जाता है.

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

रेफ़रंस