Flipbook Codepen May 2026

(සිංහල ගීතිකා කෝඩ් යෙදුම)

"සකල ජාතීනි, සමිඳුන්ට ප්‍රශංසා කරන්න. සව් සතුනි, හිමි තුම ගුණ මහිමය වර්ණනා කරන්න. මන්ද, අප කෙරෙහි සමිඳුන්ගේ ප්‍රේමය ඉමහත්ය. එතුමන්ගේ විශ්වාසකම සදහට ම පවත්නේය. සමිඳුන්ට ප්‍රශංසා කරන්න" (ගීතාවලිය 117)."

Get our app for your favourite device

Flipbook Codepen May 2026

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>Flipbook Canvas | Interactive Draggable Animation</title>
    <style>
        * 
            user-select: none; /* Prevent accidental text selection while dragging */
            -webkit-tap-highlight-color: transparent;
body 
            background: linear-gradient(145deg, #1a2a3a 0%, #0f1a24 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: 'Segoe UI', 'Poppins', 'Courier New', monospace;
            margin: 0;
            padding: 20px;
/* Card container with soft shadow */
        .flipbook-container 
            background: rgba(30, 40, 50, 0.6);
            border-radius: 48px;
            padding: 24px 20px 20px 20px;
            box-shadow: 0 25px 45px rgba(0, 0, 0, 0.4), inset 0 1px 2px rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(2px);
canvas 
            display: block;
            margin: 0 auto;
            border-radius: 20px;
            box-shadow: 0 20px 35px rgba(0, 0, 0, 0.4), 0 0 0 8px #f9e6cf, 0 0 0 12px #c9aa7b;
            cursor: grab;
            background: #fef0da;
            transition: box-shadow 0.1s ease;
canvas:active 
            cursor: grabbing;
/* Control panel — retro & playful */
        .controls 
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 18px;
            margin-top: 28px;
            margin-bottom: 12px;
button 
            background: #2c3e2f;
            border: none;
            font-size: 1.4rem;
            font-weight: bold;
            font-family: monospace;
            padding: 8px 18px;
            border-radius: 60px;
            color: #ffe7c4;
            cursor: pointer;
            box-shadow: 0 5px 0 #1a2a1d;
            transition: 0.07s linear;
            letter-spacing: 1px;
            backdrop-filter: blur(4px);
            background: #3b4e3e;
button:active 
            transform: translateY(3px);
            box-shadow: 0 2px 0 #1a2a1d;
.page-indicator 
            background: #1e2a24d9;
            backdrop-filter: blur(12px);
            padding: 6px 22px;
            border-radius: 100px;
            font-weight: bold;
            font-size: 1.3rem;
            color: #fae6b3;
            font-family: 'Courier New', monospace;
            display: inline-flex;
            align-items: center;
            gap: 12px;
            box-shadow: inset 0 1px 3px #00000033, 0 5px 10px #0000002e;
.indicator-value 
            background: #00000055;
            padding: 2px 14px;
            border-radius: 50px;
            font-size: 1.6rem;
            min-width: 70px;
            text-align: center;
            font-weight: 800;
            color: #ffdd99;
.drag-hint 
            text-align: center;
            margin-top: 16px;
            font-size: 0.85rem;
            font-family: monospace;
            color: #c0cfb5;
            background: #00000055;
            display: inline-block;
            width: auto;
            padding: 6px 16px;
            border-radius: 50px;
            backdrop-filter: blur(4px);
.footer 
            display: flex;
            justify-content: center;
            gap: 20px;
            align-items: center;
            flex-wrap: wrap;
@media (max-width: 550px) 
            .flipbook-container 
                padding: 16px;
button 
                padding: 5px 14px;
                font-size: 1rem;
.indicator-value 
                font-size: 1.2rem;
                min-width: 55px;
</style>
</head>
<body>
<div>
    <div class="flipbook-container">
        <canvas id="flipCanvas" width="600" height="400" style="width:100%; height:auto; max-width:600px; aspect-ratio:600/400"></canvas>
<div class="controls">
            <button id="prevBtn" aria-label="previous page">◀ PREV</button>
            <div class="page-indicator">
                📖 PAGE <span id="pageNum" class="indicator-value">1</span> / <span id="totalPages">12</span>
            </div>
            <button id="nextBtn" aria-label="next page">NEXT ▶</button>
        </div>
        <div class="footer">
            <div class="drag-hint">✋ DRAG HORIZONTALLY → flip pages like a real book</div>
        </div>
    </div>
</div>
<script>
    (function()
        // ----- CONFIGURATION -----
        const canvas = document.getElementById('flipCanvas');
        const ctx = canvas.getContext('2d');
// Flipbook settings
        const TOTAL_PAGES = 12;      // 12 pages total (6 spreads / 12 individual views)
        let currentPage = 1;         // 1-indexed page number (1 to TOTAL_PAGES)
// Dragging state
        let isDragging = false;
        let dragStartX = 0;
        let dragThreshold = 25;       // minimum drag distance to flip page (pixels)
// Animation / smoothing for page turns (optional subtle effect)
        let transitionOffset = 0;      // not used for persistent drag, just for UX feedback
        let flipInProgress = false;
// ----- DRAWING ENGINE: each page has a unique vibrant illustration -----
        // All drawing is based on the current page number. Each page is handcrafted with retro flipbook energy.
function drawPage(pageNumber) 
            if (!ctx) return;
// clear canvas with warm paper texture
            ctx.clearRect(0, 0, canvas.width, canvas.height);
// background: vintage paper effect (soft grain)
            const grad = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
            grad.addColorStop(0, '#fff6e8');
            grad.addColorStop(1, '#faeecd');
            ctx.fillStyle = grad;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
// subtle grid / flipbook texture
            ctx.save();
            ctx.globalAlpha = 0.12;
            for(let i = 0; i < canvas.width; i += 20) 
                ctx.beginPath();
                ctx.moveTo(i, 0);
                ctx.lineTo(i, canvas.height);
                ctx.strokeStyle = '#aa8c54';
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(0, i);
                ctx.lineTo(canvas.width, i);
                ctx.stroke();
ctx.restore();
// decorative border reminiscent of flipbook frames
            ctx.strokeStyle = '#cb9e6b';
            ctx.lineWidth = 8;
            ctx.strokeRect(12, 12, canvas.width - 24, canvas.height - 24);
            ctx.strokeStyle = '#e5c8a3';
            ctx.lineWidth = 2;
            ctx.strokeRect(18, 18, canvas.width - 36, canvas.height - 36);
// draw the unique page content based on page number
            // each page delivers a distinct sketch / icon and a fun fact or short phrase
            ctx.font = `bold $Math.floor(canvas.height * 0.09)px "Courier New", monospace`;
            ctx.fillStyle = '#4e3b28';
            ctx.shadowBlur = 0;
// page header: small flipbook indicator
            ctx.font = `12px monospace`;
            ctx.fillStyle = '#b48b5a';
            ctx.fillText(`✦ page $pageNumber ✦`, canvas.width - 70, 35);
            ctx.fillStyle = '#7c5e3c';
            ctx.fillText(`flip·book`, 20, 35);
// main content style
            ctx.font = `500 $Math.floor(canvas.height * 0.06)px "Segoe UI", "Courier New", monospace`;
            ctx.fillStyle = '#3a2c1e';
            ctx.shadowBlur = 0;
// ---- creative illustration per page ----
            // Center coordinates for icon
            const centerX = canvas.width/2;
            const centerY = canvas.height/2 - 10;
            const iconSize = Math.min(canvas.width * 0.2, 80);
// draw each page uniquely
            switch(pageNumber) 
                case 1:
                    drawStickFigure(centerX, centerY, iconSize);
                    ctx.fillText("✨ THE BEGINNING", centerX-70, centerY+50);
                    ctx.font = `14px monospace`;
                    ctx.fillStyle = '#6e583f';
                    ctx.fillText("Every story starts with a flip", centerX-100, centerY+95);
                    break;
                case 2:
                    drawSunburst(centerX, centerY, iconSize);
                    ctx.fillText("☀️ SUNRISE", centerX-55, centerY+55);
                    ctx.font = `italic 14px monospace`;
                    ctx.fillText("morning glow", centerX-45, centerY+95);
                    break;
                case 3:
                    drawWave(centerX, centerY, iconSize);
                    ctx.fillText("🌊 OCEAN WAVE", centerX-70, centerY+55);
                    ctx.fillText("endless motion", centerX-60, centerY+95);
                    break;
                case 4:
                    drawTree(centerX, centerY, iconSize);
                    ctx.fillText("🌳 OAK TREE", centerX-60, centerY+55);
                    ctx.fillText("roots & leaves", centerX-55, centerY+95);
                    break;
                case 5:
                    drawStar(centerX, centerY, iconSize);
                    ctx.fillText("⭐ SHOOTING STAR", centerX-85, centerY+55);
                    ctx.fillText("make a wish", centerX-55, centerY+95);
                    break;
                case 6:
                    drawHeart(centerX, centerY, iconSize);
                    ctx.fillText("❤️ HEARTBEAT", centerX-68, centerY+55);
                    ctx.fillStyle = "#8b3c3c";
                    ctx.fillText("thump thump", centerX-50, centerY+95);
                    break;
                case 7:
                    drawRocket(centerX, centerY, iconSize);
                    ctx.fillText("🚀 TO THE MOON", centerX-80, centerY+55);
                    ctx.fillText("adventure awaits", centerX-70, centerY+95);
                    break;
                case 8:
                    drawButterfly(centerX, centerY, iconSize);
                    ctx.fillText("🦋 METAMORPHOSIS", centerX-95, centerY+55);
                    ctx.fillText("spread your wings", centerX-70, centerY+95);
                    break;
                case 9:
                    drawCoffee(centerX, centerY, iconSize);
                    ctx.fillText("☕ COFFEE BREAK", centerX-80, centerY+55);
                    ctx.fillText("stay animated", centerX-55, centerY+95);
                    break;
                case 10:
                    drawMountain(centerX, centerY, iconSize);
                    ctx.fillText("⛰️ PEAK", centerX-45, centerY+55);
                    ctx.fillText("higher every frame", centerX-70, centerY+95);
                    break;
                case 11:
                    drawBookStack(centerX, centerY, iconSize);
                    ctx.fillText("📚 FLIPBOOK MAGIC", centerX-85, centerY+55);
                    ctx.fillText("pages in motion", centerX-65, centerY+95);
                    break;
                case 12:
                    drawFireworks(centerX, centerY, iconSize);
                    ctx.fillText("🎆 THE END?", centerX-60, centerY+55);
                    ctx.fillText("... or just a new flip", centerX-70, centerY+95);
                    break;
                default:
                    drawStickFigure(centerX, centerY, iconSize);
                    ctx.fillText(`page $pageNumber`, centerX-40, centerY+55);
                    break;
// add page curl effect: small shadow on right edge
            ctx.save();
            ctx.shadowBlur = 0;
            ctx.beginPath();
            ctx.moveTo(canvas.width-10, 10);
            ctx.quadraticCurveTo(canvas.width, 20, canvas.width-12, canvas.height-15);
            ctx.lineTo(canvas.width-25, canvas.height-5);
            ctx.fillStyle = '#ddc6a388';
            ctx.fill();
            ctx.restore();
// footnote: classic flipbook vibe
            ctx.font = `9px monospace`;
            ctx.fillStyle = '#a98754';
            ctx.fillText("◀ drag edge to flip ▶", canvas.width-130, canvas.height-12);
// ----- helper illustration functions (minimal but expressive) -----
        function drawStickFigure(x, y, size) 
            ctx.beginPath();
            ctx.arc(x, y-size*0.2, size*0.2, 0, Math.PI*2);
            ctx.fillStyle = '#4a3624';
            ctx.fill();
            ctx.beginPath();
            ctx.moveTo(x, y-size*0.02);
            ctx.lineTo(x, y+size*0.25);
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(x-size*0.18, y+size*0.08);
            ctx.lineTo(x+size*0.18, y+size*0.08);
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(x, y+size*0.25);
            ctx.lineTo(x-size*0.2, y+size*0.45);
            ctx.moveTo(x, y+size*0.25);
            ctx.lineTo(x+size*0.2, y+size*0.45);
            ctx.stroke();
function drawSunburst(x,y,s) 
            for(let i=0;i<12;i++) 
                let angle = i * Math.PI*2/12;
                let x2 = x+Math.cos(angle)*s*0.7;
                let y2 = y+Math.sin(angle)*s*0.7;
                ctx.beginPath();
                ctx.moveTo(x,y);
                ctx.lineTo(x2,y2);
                ctx.lineWidth=5;
                ctx.strokeStyle='#f7b32b';
                ctx.stroke();
ctx.beginPath();
            ctx.arc(x,y,s*0.25,0,Math.PI*2);
            ctx.fillStyle='#ffcf40';
            ctx.fill();
function drawWave(x,y,s) 
            ctx.beginPath();
            for(let i=0;i<=4;i++) 
                let px = x - s*0.6 + i*(s*0.3);
                let py = y + Math.sin(i*1.2)*s*0.2;
                if(i===0) ctx.moveTo(px,py);
                else ctx.lineTo(px,py);
ctx.strokeStyle='#2c6e9e';
            ctx.lineWidth=4;
            ctx.stroke();
function drawTree(x,y,s) 
            ctx.fillStyle = '#b87c4f';
            ctx.fillRect(x-s*0.08, y-s*0.1, s*0.16, s*0.5);
            ctx.fillStyle = '#5f8b4c';
            ctx.beginPath();
            ctx.arc(x, y-s*0.25, s*0.35, 0, Math.PI*2);
            ctx.fill();
function drawStar(x,y,s) 
            let spikes=5;
            let outer=s*0.6;
            let inner=s*0.25;
            let step=Math.PI/spikes;
            ctx.beginPath();
            for(let i=0;i<2*spikes;i++)
                let r = (i%2===0)?outer:inner;
                let angle = i*step - Math.PI/2;
                let px = x+Math.cos(angle)*r;
                let py = y+Math.sin(angle)*r;
                if(i===0) ctx.moveTo(px,py);
                else ctx.lineTo(px,py);
ctx.closePath();
            ctx.fillStyle='#f3c26b';
            ctx.fill();
function drawHeart(x,y,s) 
            ctx.beginPath();
            let topCurve = s*0.4;
            ctx.moveTo(x, y+topCurve*0.4);
            ctx.bezierCurveTo(x-topCurve, y-topCurve, x-topCurve*1.2, y+topCurve*0.9, x, y+topCurve*1.1);
            ctx.bezierCurveTo(x+topCurve*1.2, y+topCurve*0.9, x+topCurve, y-topCurve, x, y+topCurve*0.4);
            ctx.fillStyle='#e34242';
            ctx.fill();
function drawRocket(x,y,s) 
            ctx.fillStyle='#9f7e69';
            ctx.fillRect(x-s*0.12, y-s*0.05, s*0.24, s*0.5);
            ctx.beginPath();
            ctx.moveTo(x-s*0.18, y+s*0.45);
            ctx.lineTo(x, y+s*0.7);
            ctx.lineTo(x+s*0.18, y+s*0.45);
            ctx.fill();
            ctx.fillStyle='#df5e2a';
            ctx.beginPath();
            ctx.ellipse(x, y-s*0.05, s*0.22, s*0.28, 0, 0, Math.PI*2);
            ctx.fill();
function drawButterfly(x,y,s)
            ctx.fillStyle='#dc9e6f';
            ctx.beginPath(); ctx.ellipse(x-s*0.3, y, s*0.3, s*0.2, -0.5, 0, 2*Math.PI); ctx.fill();
            ctx.beginPath(); ctx.ellipse(x+s*0.3, y, s*0.3, s*0.2, 0.5, 0, 2*Math.PI); ctx.fill();
            ctx.fillStyle='#885e3e';
            ctx.fillRect(x-s*0.05, y-s*0.05, s*0.1, s*0.25);
function drawCoffee(x,y,s)
            ctx.fillStyle='#af7f51';
            ctx.fillRect(x-s*0.2, y-s*0.2, s*0.4, s*0.45);
            ctx.beginPath(); ctx.ellipse(x+s*0.25, y-s*0.02, s*0.1, s*0.18, 0, 0, 2*Math.PI); ctx.fill();
function drawMountain(x,y,s)
            ctx.beginPath(); ctx.moveTo(x-s*0.5, y+s*0.2);
            ctx.lineTo(x, y-s*0.3); ctx.lineTo(x+s*0.5, y+s*0.2); ctx.fillStyle='#7d9e6b'; ctx.fill();
            ctx.fillStyle='white'; ctx.beginPath(); ctx.moveTo(x-s*0.1, y-s*0.05); ctx.lineTo(x, y-s*0.2); ctx.lineTo(x+s*0.1, y-s*0.05); ctx.fill();
function drawBookStack(x,y,s)
            for(let i=0;i<3;i++) ctx.fillStyle = `#bd9a6$40+i*5`; ctx.fillRect(x-s*0.3+(i*4), y-s*0.2+(i*5), s*0.6, s*0.12); 
            ctx.fillStyle='#ab8a54'; ctx.fillRect(x-s*0.25, y-s*0.23, s*0.5, 8);
function drawFireworks(x,y,s)
            for(let i=0;i<20;i++) let angle=Math.random()*6.28; let r=Math.random()*s*0.7; let x2=x+Math.cos(angle)*r; let y2=y+Math.sin(angle)*r; ctx.beginPath(); ctx.arc(x2,y2,3,0,2*Math.PI); ctx.fillStyle=`hsl($Math.random()*360,70%,60%)`; ctx.fill();
// update canvas and page indicator text
        function renderCurrentPage() 
            drawPage(currentPage);
            const pageSpan = document.getElementById('pageNum');
            if(pageSpan) pageSpan.innerText = currentPage;
// navigate to a specific page safely
        function goToPage(page) 
            if(page < 1) page = 1;
            if(page > TOTAL_PAGES) page = TOTAL_PAGES;
            if(currentPage === page) return;
            currentPage = page;
            renderCurrentPage();
function nextPage() 
            if(currentPage < TOTAL_PAGES) 
                goToPage(currentPage + 1);
             else 
                // playful hint: add a little bounce effect to show it's the last page
                canvas.style.transform = 'scale(0.99)';
                setTimeout(()=> canvas.style.transform = ''; , 120);
function prevPage() 
            if(currentPage > 1) 
                goToPage(currentPage - 1);
             else 
                canvas.style.transform = 'scale(0.99)';
                setTimeout(()=> canvas.style.transform = ''; , 120);
// ----- DRAG TO FLIP LOGIC (flipbook style) -----
        function onPointerStart(e) 
            e.preventDefault();
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const clientX = e.clientX ?? (e.touches ? e.touches[0].clientX : 0);
            dragStartX = (clientX - rect.left) * scaleX;
            isDragging = true;
            canvas.style.cursor = 'grabbing';
function onPointerMove(e) 
            if(!isDragging) return;
            e.preventDefault();
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const clientX = e.clientX ?? (e.touches ? e.touches[0].clientX : 0);
            let currentDragX = (clientX - rect.left) * scaleX;
            let deltaX = currentDragX - dragStartX;
// if the drag exceeds threshold, flip page and reset drag
            if(Math.abs(deltaX) >= dragThreshold) 
                if(deltaX > 0) 
                    // drag right -> previous page (like pulling from left edge)
                    if(currentPage > 1) 
                        prevPage();
else 
                    // drag left -> next page
                    if(currentPage < TOTAL_PAGES) 
                        nextPage();
// reset drag state to avoid multiple flips per gesture
                isDragging = false;
                canvas.style.cursor = 'grab';
                // tiny haptic feedback via transform flash
                canvas.style.transform = 'scale(0.98)';
                setTimeout(()=> canvas.style.transform = ''; , 100);
                dragStartX = 0;
function onPointerEnd(e) 
            if(isDragging) 
                isDragging = false;
                canvas.style.cursor = 'grab';
// attach both mouse and touch events
        function attachDragEvents() 
            canvas.addEventListener('mousedown', onPointerStart);
            window.addEventListener('mousemove', onPointerMove);
            window.addEventListener('mouseup', onPointerEnd);
canvas.addEventListener('touchstart', onPointerStart, passive: false);
            window.addEventListener('touchmove', onPointerMove, passive: false);
            window.addEventListener('touchend', onPointerEnd);
            canvas.addEventListener('dragstart', (e) => e.preventDefault());
            canvas.style.cursor = 'grab';
// Buttons
        document.getElementById('prevBtn').addEventListener('click', prevPage);
        document.getElementById('nextBtn').addEventListener('click', nextPage);
// Set total pages span
        const totalSpan = document.getElementById('totalPages');
        if(totalSpan) totalSpan.innerText = TOTAL_PAGES;
// Initial draw with first page
        renderCurrentPage();
        attachDragEvents();
// optional: resize handling - keep crisp ratio
        window.addEventListener('resize', () => 
            renderCurrentPage();
        );
    )();
</script>
</body>
</html>

Creating a flipbook animation on is a popular project for web developers looking to master 3D CSS transforms and JavaScript interactions. These digital flipbooks simulate the tactile feel of physical books with smooth page-turning effects. 1. The Core Technology Most modern flipbooks on CodePen rely on CSS 3D Transforms transform-style: preserve-3d;

: This property is applied to the book container to ensure that child elements (the pages) exist in a 3D space rather than being flattened against the screen. perspective

: Set on a parent element, this defines how "far" the user is from the 3D object, making the flip appear realistic rather than distorted. backface-visibility: hidden;

: Essential for preventing the "back" of a page from showing through the front while it is flipping. 2. Common Implementation Methods Developers typically use one of two approaches: : Uses the

state of hidden checkboxes or radio buttons to trigger animations. When a user "clicks" (toggles the checkbox), the page's transform: rotateY() value changes from 0 raised to the composed with power negative 180 raised to the composed with power JavaScript/GSAP flipbook codepen

: For more complex interactions—like dragging a page corner or having a variable number of pages—libraries like GSAP (GreenSock) or plugins like are used to handle the math and physics of the turn. 3. Structural Breakdown A standard flipbook pen is usually structured as follows: Book Container

: The relative-positioned wrapper that holds the perspective.

: Absolute-positioned divs stacked on top of each other. Each page is divided into a "front" and "back" face. Stacking Order (

: This is the trickiest part. Developers must dynamically update the Creating a flipbook animation on is a popular

as pages flip so that the currently moving page stays on top of the others. 4. Top Examples on CodePen

Searching for "flipbook" on CodePen reveals several high-quality templates: Interactive Book : Features realistic page shadows and a "hardcover" effect. Magazine Layout

: Focuses on high-quality imagery and typography that shifts slightly as the page bends. Photo Album

: Uses simple left/right click navigation with a clean, minimalist 3D flip. 5. Learning Resources If you want to build one yourself, check out these guides: CSS-Tricks: Using Pure CSS for Flipbook Style Animation for a deep dive into the logic. MDN Web Docs on 3D Transforms to understand the underlying properties. to start your own flipbook project? the shadow rendering

Conclusion: Start Your Flipbook Project Today

The journey from "I need a flipbook" to "Here is my interactive brochure" is remarkably short if you leverage Codepen. The community has already solved the hardest parts: the 3D vertex math, the shadow rendering, and the drag detection.

By searching for "flipbook codepen" , you are not starting from scratch. You are standing on the shoulders of creative giants. Find a pen that matches your aesthetic (minimalist, realistic, or 3D), fork it, drop in your images, and tweak the CSS duration.

Whether you are building a poetry zine, a product catalog, or a nostalgic photo album, the tactile satisfaction of a digital page turn turns passive viewing into active reading.

Ready to flip? Open a new tab, type flipbook codepen into your search bar, and start forking the future of digital publishing.

Issue: "The pages are overlapping and look like a mess."

Solution: You forgot backface-visibility: hidden;. Without this, when a page rotates 180 degrees, you see the back of the front page instead of the front of the back page. Add this to your .page, .front, .back rules.

2. Scroll-based Flip

Bind the frame change to wheel events or page scroll percentage. Feels like flipping a real book.

canvas.addEventListener('wheel', (e) => 
  if (e.deltaY > 0) 
    currentFrame = Math.min(totalFrames-1, currentFrame+1);
   else 
    currentFrame = Math.max(0, currentFrame-1);
drawFrame(currentFrame);
);
SEARCH FILTER
About Us - Kithu Bathi Gee Daham
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>Flipbook Canvas | Interactive Draggable Animation</title>
    <style>
        * 
            user-select: none; /* Prevent accidental text selection while dragging */
            -webkit-tap-highlight-color: transparent;
body 
            background: linear-gradient(145deg, #1a2a3a 0%, #0f1a24 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: 'Segoe UI', 'Poppins', 'Courier New', monospace;
            margin: 0;
            padding: 20px;
/* Card container with soft shadow */
        .flipbook-container 
            background: rgba(30, 40, 50, 0.6);
            border-radius: 48px;
            padding: 24px 20px 20px 20px;
            box-shadow: 0 25px 45px rgba(0, 0, 0, 0.4), inset 0 1px 2px rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(2px);
canvas 
            display: block;
            margin: 0 auto;
            border-radius: 20px;
            box-shadow: 0 20px 35px rgba(0, 0, 0, 0.4), 0 0 0 8px #f9e6cf, 0 0 0 12px #c9aa7b;
            cursor: grab;
            background: #fef0da;
            transition: box-shadow 0.1s ease;
canvas:active 
            cursor: grabbing;
/* Control panel — retro & playful */
        .controls 
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 18px;
            margin-top: 28px;
            margin-bottom: 12px;
button 
            background: #2c3e2f;
            border: none;
            font-size: 1.4rem;
            font-weight: bold;
            font-family: monospace;
            padding: 8px 18px;
            border-radius: 60px;
            color: #ffe7c4;
            cursor: pointer;
            box-shadow: 0 5px 0 #1a2a1d;
            transition: 0.07s linear;
            letter-spacing: 1px;
            backdrop-filter: blur(4px);
            background: #3b4e3e;
button:active 
            transform: translateY(3px);
            box-shadow: 0 2px 0 #1a2a1d;
.page-indicator 
            background: #1e2a24d9;
            backdrop-filter: blur(12px);
            padding: 6px 22px;
            border-radius: 100px;
            font-weight: bold;
            font-size: 1.3rem;
            color: #fae6b3;
            font-family: 'Courier New', monospace;
            display: inline-flex;
            align-items: center;
            gap: 12px;
            box-shadow: inset 0 1px 3px #00000033, 0 5px 10px #0000002e;
.indicator-value 
            background: #00000055;
            padding: 2px 14px;
            border-radius: 50px;
            font-size: 1.6rem;
            min-width: 70px;
            text-align: center;
            font-weight: 800;
            color: #ffdd99;
.drag-hint 
            text-align: center;
            margin-top: 16px;
            font-size: 0.85rem;
            font-family: monospace;
            color: #c0cfb5;
            background: #00000055;
            display: inline-block;
            width: auto;
            padding: 6px 16px;
            border-radius: 50px;
            backdrop-filter: blur(4px);
.footer 
            display: flex;
            justify-content: center;
            gap: 20px;
            align-items: center;
            flex-wrap: wrap;
@media (max-width: 550px) 
            .flipbook-container 
                padding: 16px;
button 
                padding: 5px 14px;
                font-size: 1rem;
.indicator-value 
                font-size: 1.2rem;
                min-width: 55px;
</style>
</head>
<body>
<div>
    <div class="flipbook-container">
        <canvas id="flipCanvas" width="600" height="400" style="width:100%; height:auto; max-width:600px; aspect-ratio:600/400"></canvas>
<div class="controls">
            <button id="prevBtn" aria-label="previous page">◀ PREV</button>
            <div class="page-indicator">
                📖 PAGE <span id="pageNum" class="indicator-value">1</span> / <span id="totalPages">12</span>
            </div>
            <button id="nextBtn" aria-label="next page">NEXT ▶</button>
        </div>
        <div class="footer">
            <div class="drag-hint">✋ DRAG HORIZONTALLY → flip pages like a real book</div>
        </div>
    </div>
</div>
<script>
    (function()
        // ----- CONFIGURATION -----
        const canvas = document.getElementById('flipCanvas');
        const ctx = canvas.getContext('2d');
// Flipbook settings
        const TOTAL_PAGES = 12;      // 12 pages total (6 spreads / 12 individual views)
        let currentPage = 1;         // 1-indexed page number (1 to TOTAL_PAGES)
// Dragging state
        let isDragging = false;
        let dragStartX = 0;
        let dragThreshold = 25;       // minimum drag distance to flip page (pixels)
// Animation / smoothing for page turns (optional subtle effect)
        let transitionOffset = 0;      // not used for persistent drag, just for UX feedback
        let flipInProgress = false;
// ----- DRAWING ENGINE: each page has a unique vibrant illustration -----
        // All drawing is based on the current page number. Each page is handcrafted with retro flipbook energy.
function drawPage(pageNumber) 
            if (!ctx) return;
// clear canvas with warm paper texture
            ctx.clearRect(0, 0, canvas.width, canvas.height);
// background: vintage paper effect (soft grain)
            const grad = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
            grad.addColorStop(0, '#fff6e8');
            grad.addColorStop(1, '#faeecd');
            ctx.fillStyle = grad;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
// subtle grid / flipbook texture
            ctx.save();
            ctx.globalAlpha = 0.12;
            for(let i = 0; i < canvas.width; i += 20) 
                ctx.beginPath();
                ctx.moveTo(i, 0);
                ctx.lineTo(i, canvas.height);
                ctx.strokeStyle = '#aa8c54';
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(0, i);
                ctx.lineTo(canvas.width, i);
                ctx.stroke();
ctx.restore();
// decorative border reminiscent of flipbook frames
            ctx.strokeStyle = '#cb9e6b';
            ctx.lineWidth = 8;
            ctx.strokeRect(12, 12, canvas.width - 24, canvas.height - 24);
            ctx.strokeStyle = '#e5c8a3';
            ctx.lineWidth = 2;
            ctx.strokeRect(18, 18, canvas.width - 36, canvas.height - 36);
// draw the unique page content based on page number
            // each page delivers a distinct sketch / icon and a fun fact or short phrase
            ctx.font = `bold $Math.floor(canvas.height * 0.09)px "Courier New", monospace`;
            ctx.fillStyle = '#4e3b28';
            ctx.shadowBlur = 0;
// page header: small flipbook indicator
            ctx.font = `12px monospace`;
            ctx.fillStyle = '#b48b5a';
            ctx.fillText(`✦ page $pageNumber ✦`, canvas.width - 70, 35);
            ctx.fillStyle = '#7c5e3c';
            ctx.fillText(`flip·book`, 20, 35);
// main content style
            ctx.font = `500 $Math.floor(canvas.height * 0.06)px "Segoe UI", "Courier New", monospace`;
            ctx.fillStyle = '#3a2c1e';
            ctx.shadowBlur = 0;
// ---- creative illustration per page ----
            // Center coordinates for icon
            const centerX = canvas.width/2;
            const centerY = canvas.height/2 - 10;
            const iconSize = Math.min(canvas.width * 0.2, 80);
// draw each page uniquely
            switch(pageNumber) 
                case 1:
                    drawStickFigure(centerX, centerY, iconSize);
                    ctx.fillText("✨ THE BEGINNING", centerX-70, centerY+50);
                    ctx.font = `14px monospace`;
                    ctx.fillStyle = '#6e583f';
                    ctx.fillText("Every story starts with a flip", centerX-100, centerY+95);
                    break;
                case 2:
                    drawSunburst(centerX, centerY, iconSize);
                    ctx.fillText("☀️ SUNRISE", centerX-55, centerY+55);
                    ctx.font = `italic 14px monospace`;
                    ctx.fillText("morning glow", centerX-45, centerY+95);
                    break;
                case 3:
                    drawWave(centerX, centerY, iconSize);
                    ctx.fillText("🌊 OCEAN WAVE", centerX-70, centerY+55);
                    ctx.fillText("endless motion", centerX-60, centerY+95);
                    break;
                case 4:
                    drawTree(centerX, centerY, iconSize);
                    ctx.fillText("🌳 OAK TREE", centerX-60, centerY+55);
                    ctx.fillText("roots & leaves", centerX-55, centerY+95);
                    break;
                case 5:
                    drawStar(centerX, centerY, iconSize);
                    ctx.fillText("⭐ SHOOTING STAR", centerX-85, centerY+55);
                    ctx.fillText("make a wish", centerX-55, centerY+95);
                    break;
                case 6:
                    drawHeart(centerX, centerY, iconSize);
                    ctx.fillText("❤️ HEARTBEAT", centerX-68, centerY+55);
                    ctx.fillStyle = "#8b3c3c";
                    ctx.fillText("thump thump", centerX-50, centerY+95);
                    break;
                case 7:
                    drawRocket(centerX, centerY, iconSize);
                    ctx.fillText("🚀 TO THE MOON", centerX-80, centerY+55);
                    ctx.fillText("adventure awaits", centerX-70, centerY+95);
                    break;
                case 8:
                    drawButterfly(centerX, centerY, iconSize);
                    ctx.fillText("🦋 METAMORPHOSIS", centerX-95, centerY+55);
                    ctx.fillText("spread your wings", centerX-70, centerY+95);
                    break;
                case 9:
                    drawCoffee(centerX, centerY, iconSize);
                    ctx.fillText("☕ COFFEE BREAK", centerX-80, centerY+55);
                    ctx.fillText("stay animated", centerX-55, centerY+95);
                    break;
                case 10:
                    drawMountain(centerX, centerY, iconSize);
                    ctx.fillText("⛰️ PEAK", centerX-45, centerY+55);
                    ctx.fillText("higher every frame", centerX-70, centerY+95);
                    break;
                case 11:
                    drawBookStack(centerX, centerY, iconSize);
                    ctx.fillText("📚 FLIPBOOK MAGIC", centerX-85, centerY+55);
                    ctx.fillText("pages in motion", centerX-65, centerY+95);
                    break;
                case 12:
                    drawFireworks(centerX, centerY, iconSize);
                    ctx.fillText("🎆 THE END?", centerX-60, centerY+55);
                    ctx.fillText("... or just a new flip", centerX-70, centerY+95);
                    break;
                default:
                    drawStickFigure(centerX, centerY, iconSize);
                    ctx.fillText(`page $pageNumber`, centerX-40, centerY+55);
                    break;
// add page curl effect: small shadow on right edge
            ctx.save();
            ctx.shadowBlur = 0;
            ctx.beginPath();
            ctx.moveTo(canvas.width-10, 10);
            ctx.quadraticCurveTo(canvas.width, 20, canvas.width-12, canvas.height-15);
            ctx.lineTo(canvas.width-25, canvas.height-5);
            ctx.fillStyle = '#ddc6a388';
            ctx.fill();
            ctx.restore();
// footnote: classic flipbook vibe
            ctx.font = `9px monospace`;
            ctx.fillStyle = '#a98754';
            ctx.fillText("◀ drag edge to flip ▶", canvas.width-130, canvas.height-12);
// ----- helper illustration functions (minimal but expressive) -----
        function drawStickFigure(x, y, size) 
            ctx.beginPath();
            ctx.arc(x, y-size*0.2, size*0.2, 0, Math.PI*2);
            ctx.fillStyle = '#4a3624';
            ctx.fill();
            ctx.beginPath();
            ctx.moveTo(x, y-size*0.02);
            ctx.lineTo(x, y+size*0.25);
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(x-size*0.18, y+size*0.08);
            ctx.lineTo(x+size*0.18, y+size*0.08);
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(x, y+size*0.25);
            ctx.lineTo(x-size*0.2, y+size*0.45);
            ctx.moveTo(x, y+size*0.25);
            ctx.lineTo(x+size*0.2, y+size*0.45);
            ctx.stroke();
function drawSunburst(x,y,s) 
            for(let i=0;i<12;i++) 
                let angle = i * Math.PI*2/12;
                let x2 = x+Math.cos(angle)*s*0.7;
                let y2 = y+Math.sin(angle)*s*0.7;
                ctx.beginPath();
                ctx.moveTo(x,y);
                ctx.lineTo(x2,y2);
                ctx.lineWidth=5;
                ctx.strokeStyle='#f7b32b';
                ctx.stroke();
ctx.beginPath();
            ctx.arc(x,y,s*0.25,0,Math.PI*2);
            ctx.fillStyle='#ffcf40';
            ctx.fill();
function drawWave(x,y,s) 
            ctx.beginPath();
            for(let i=0;i<=4;i++) 
                let px = x - s*0.6 + i*(s*0.3);
                let py = y + Math.sin(i*1.2)*s*0.2;
                if(i===0) ctx.moveTo(px,py);
                else ctx.lineTo(px,py);
ctx.strokeStyle='#2c6e9e';
            ctx.lineWidth=4;
            ctx.stroke();
function drawTree(x,y,s) 
            ctx.fillStyle = '#b87c4f';
            ctx.fillRect(x-s*0.08, y-s*0.1, s*0.16, s*0.5);
            ctx.fillStyle = '#5f8b4c';
            ctx.beginPath();
            ctx.arc(x, y-s*0.25, s*0.35, 0, Math.PI*2);
            ctx.fill();
function drawStar(x,y,s) 
            let spikes=5;
            let outer=s*0.6;
            let inner=s*0.25;
            let step=Math.PI/spikes;
            ctx.beginPath();
            for(let i=0;i<2*spikes;i++)
                let r = (i%2===0)?outer:inner;
                let angle = i*step - Math.PI/2;
                let px = x+Math.cos(angle)*r;
                let py = y+Math.sin(angle)*r;
                if(i===0) ctx.moveTo(px,py);
                else ctx.lineTo(px,py);
ctx.closePath();
            ctx.fillStyle='#f3c26b';
            ctx.fill();
function drawHeart(x,y,s) 
            ctx.beginPath();
            let topCurve = s*0.4;
            ctx.moveTo(x, y+topCurve*0.4);
            ctx.bezierCurveTo(x-topCurve, y-topCurve, x-topCurve*1.2, y+topCurve*0.9, x, y+topCurve*1.1);
            ctx.bezierCurveTo(x+topCurve*1.2, y+topCurve*0.9, x+topCurve, y-topCurve, x, y+topCurve*0.4);
            ctx.fillStyle='#e34242';
            ctx.fill();
function drawRocket(x,y,s) 
            ctx.fillStyle='#9f7e69';
            ctx.fillRect(x-s*0.12, y-s*0.05, s*0.24, s*0.5);
            ctx.beginPath();
            ctx.moveTo(x-s*0.18, y+s*0.45);
            ctx.lineTo(x, y+s*0.7);
            ctx.lineTo(x+s*0.18, y+s*0.45);
            ctx.fill();
            ctx.fillStyle='#df5e2a';
            ctx.beginPath();
            ctx.ellipse(x, y-s*0.05, s*0.22, s*0.28, 0, 0, Math.PI*2);
            ctx.fill();
function drawButterfly(x,y,s)
            ctx.fillStyle='#dc9e6f';
            ctx.beginPath(); ctx.ellipse(x-s*0.3, y, s*0.3, s*0.2, -0.5, 0, 2*Math.PI); ctx.fill();
            ctx.beginPath(); ctx.ellipse(x+s*0.3, y, s*0.3, s*0.2, 0.5, 0, 2*Math.PI); ctx.fill();
            ctx.fillStyle='#885e3e';
            ctx.fillRect(x-s*0.05, y-s*0.05, s*0.1, s*0.25);
function drawCoffee(x,y,s)
            ctx.fillStyle='#af7f51';
            ctx.fillRect(x-s*0.2, y-s*0.2, s*0.4, s*0.45);
            ctx.beginPath(); ctx.ellipse(x+s*0.25, y-s*0.02, s*0.1, s*0.18, 0, 0, 2*Math.PI); ctx.fill();
function drawMountain(x,y,s)
            ctx.beginPath(); ctx.moveTo(x-s*0.5, y+s*0.2);
            ctx.lineTo(x, y-s*0.3); ctx.lineTo(x+s*0.5, y+s*0.2); ctx.fillStyle='#7d9e6b'; ctx.fill();
            ctx.fillStyle='white'; ctx.beginPath(); ctx.moveTo(x-s*0.1, y-s*0.05); ctx.lineTo(x, y-s*0.2); ctx.lineTo(x+s*0.1, y-s*0.05); ctx.fill();
function drawBookStack(x,y,s)
            for(let i=0;i<3;i++) ctx.fillStyle = `#bd9a6$40+i*5`; ctx.fillRect(x-s*0.3+(i*4), y-s*0.2+(i*5), s*0.6, s*0.12); 
            ctx.fillStyle='#ab8a54'; ctx.fillRect(x-s*0.25, y-s*0.23, s*0.5, 8);
function drawFireworks(x,y,s)
            for(let i=0;i<20;i++) let angle=Math.random()*6.28; let r=Math.random()*s*0.7; let x2=x+Math.cos(angle)*r; let y2=y+Math.sin(angle)*r; ctx.beginPath(); ctx.arc(x2,y2,3,0,2*Math.PI); ctx.fillStyle=`hsl($Math.random()*360,70%,60%)`; ctx.fill();
// update canvas and page indicator text
        function renderCurrentPage() 
            drawPage(currentPage);
            const pageSpan = document.getElementById('pageNum');
            if(pageSpan) pageSpan.innerText = currentPage;
// navigate to a specific page safely
        function goToPage(page) 
            if(page < 1) page = 1;
            if(page > TOTAL_PAGES) page = TOTAL_PAGES;
            if(currentPage === page) return;
            currentPage = page;
            renderCurrentPage();
function nextPage() 
            if(currentPage < TOTAL_PAGES) 
                goToPage(currentPage + 1);
             else 
                // playful hint: add a little bounce effect to show it's the last page
                canvas.style.transform = 'scale(0.99)';
                setTimeout(()=> canvas.style.transform = ''; , 120);
function prevPage() 
            if(currentPage > 1) 
                goToPage(currentPage - 1);
             else 
                canvas.style.transform = 'scale(0.99)';
                setTimeout(()=> canvas.style.transform = ''; , 120);
// ----- DRAG TO FLIP LOGIC (flipbook style) -----
        function onPointerStart(e) 
            e.preventDefault();
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const clientX = e.clientX ?? (e.touches ? e.touches[0].clientX : 0);
            dragStartX = (clientX - rect.left) * scaleX;
            isDragging = true;
            canvas.style.cursor = 'grabbing';
function onPointerMove(e) 
            if(!isDragging) return;
            e.preventDefault();
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const clientX = e.clientX ?? (e.touches ? e.touches[0].clientX : 0);
            let currentDragX = (clientX - rect.left) * scaleX;
            let deltaX = currentDragX - dragStartX;
// if the drag exceeds threshold, flip page and reset drag
            if(Math.abs(deltaX) >= dragThreshold) 
                if(deltaX > 0) 
                    // drag right -> previous page (like pulling from left edge)
                    if(currentPage > 1) 
                        prevPage();
else 
                    // drag left -> next page
                    if(currentPage < TOTAL_PAGES) 
                        nextPage();
// reset drag state to avoid multiple flips per gesture
                isDragging = false;
                canvas.style.cursor = 'grab';
                // tiny haptic feedback via transform flash
                canvas.style.transform = 'scale(0.98)';
                setTimeout(()=> canvas.style.transform = ''; , 100);
                dragStartX = 0;
function onPointerEnd(e) 
            if(isDragging) 
                isDragging = false;
                canvas.style.cursor = 'grab';
// attach both mouse and touch events
        function attachDragEvents() 
            canvas.addEventListener('mousedown', onPointerStart);
            window.addEventListener('mousemove', onPointerMove);
            window.addEventListener('mouseup', onPointerEnd);
canvas.addEventListener('touchstart', onPointerStart, passive: false);
            window.addEventListener('touchmove', onPointerMove, passive: false);
            window.addEventListener('touchend', onPointerEnd);
            canvas.addEventListener('dragstart', (e) => e.preventDefault());
            canvas.style.cursor = 'grab';
// Buttons
        document.getElementById('prevBtn').addEventListener('click', prevPage);
        document.getElementById('nextBtn').addEventListener('click', nextPage);
// Set total pages span
        const totalSpan = document.getElementById('totalPages');
        if(totalSpan) totalSpan.innerText = TOTAL_PAGES;
// Initial draw with first page
        renderCurrentPage();
        attachDragEvents();
// optional: resize handling - keep crisp ratio
        window.addEventListener('resize', () => 
            renderCurrentPage();
        );
    )();
</script>
</body>
</html>

Creating a flipbook animation on is a popular project for web developers looking to master 3D CSS transforms and JavaScript interactions. These digital flipbooks simulate the tactile feel of physical books with smooth page-turning effects. 1. The Core Technology Most modern flipbooks on CodePen rely on CSS 3D Transforms transform-style: preserve-3d;

: This property is applied to the book container to ensure that child elements (the pages) exist in a 3D space rather than being flattened against the screen. perspective

: Set on a parent element, this defines how "far" the user is from the 3D object, making the flip appear realistic rather than distorted. backface-visibility: hidden;

: Essential for preventing the "back" of a page from showing through the front while it is flipping. 2. Common Implementation Methods Developers typically use one of two approaches: : Uses the

state of hidden checkboxes or radio buttons to trigger animations. When a user "clicks" (toggles the checkbox), the page's transform: rotateY() value changes from 0 raised to the composed with power negative 180 raised to the composed with power JavaScript/GSAP

: For more complex interactions—like dragging a page corner or having a variable number of pages—libraries like GSAP (GreenSock) or plugins like are used to handle the math and physics of the turn. 3. Structural Breakdown A standard flipbook pen is usually structured as follows: Book Container

: The relative-positioned wrapper that holds the perspective.

: Absolute-positioned divs stacked on top of each other. Each page is divided into a "front" and "back" face. Stacking Order (

: This is the trickiest part. Developers must dynamically update the

as pages flip so that the currently moving page stays on top of the others. 4. Top Examples on CodePen

Searching for "flipbook" on CodePen reveals several high-quality templates: Interactive Book : Features realistic page shadows and a "hardcover" effect. Magazine Layout

: Focuses on high-quality imagery and typography that shifts slightly as the page bends. Photo Album

: Uses simple left/right click navigation with a clean, minimalist 3D flip. 5. Learning Resources If you want to build one yourself, check out these guides: CSS-Tricks: Using Pure CSS for Flipbook Style Animation for a deep dive into the logic. MDN Web Docs on 3D Transforms to understand the underlying properties. to start your own flipbook project?

Conclusion: Start Your Flipbook Project Today

The journey from "I need a flipbook" to "Here is my interactive brochure" is remarkably short if you leverage Codepen. The community has already solved the hardest parts: the 3D vertex math, the shadow rendering, and the drag detection.

By searching for "flipbook codepen" , you are not starting from scratch. You are standing on the shoulders of creative giants. Find a pen that matches your aesthetic (minimalist, realistic, or 3D), fork it, drop in your images, and tweak the CSS duration.

Whether you are building a poetry zine, a product catalog, or a nostalgic photo album, the tactile satisfaction of a digital page turn turns passive viewing into active reading.

Ready to flip? Open a new tab, type flipbook codepen into your search bar, and start forking the future of digital publishing.

Issue: "The pages are overlapping and look like a mess."

Solution: You forgot backface-visibility: hidden;. Without this, when a page rotates 180 degrees, you see the back of the front page instead of the front of the back page. Add this to your .page, .front, .back rules.

2. Scroll-based Flip

Bind the frame change to wheel events or page scroll percentage. Feels like flipping a real book.

canvas.addEventListener('wheel', (e) => 
  if (e.deltaY > 0) 
    currentFrame = Math.min(totalFrames-1, currentFrame+1);
   else 
    currentFrame = Math.max(0, currentFrame-1);
drawFrame(currentFrame);
);