Flipbook Codepen !full! -
: 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;
.progress-slider input width: 130px;
Applied to the parent container to give child elements a 3D space.
The HTML needs to be highly structured. A standard approach involves a master wrapper containing a series of page elements.
The basic structure of a flipbook consists of a container element, which will hold the flipbook animation, and a series of child elements, which will represent each page of the flipbook. You can create these elements using HTML, and then style them using CSS. flipbook codepen
.page-3 background-image: url('image3.jpg');
: Inject a dynamically opacity-scaled linear gradient over the turning page to simulate shifting real-world ambient lighting shadows.
Add box-shadow changes during the transition to simulate light changing as the page moves.
// 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) : Set on a parent element, this defines
The Magic of Flipbooks: Top CodePen Examples and How to Build Your Own
flipbook.style.transform = `rotateY($currentPage * 90deg)`;
function drawStars(ctx, w, h) for(let i=0;i<12;i++) ctx.fillStyle=`hsl($40+i*20, 80%, 65%)`; ctx.beginPath(); ctx.arc(w*(0.2+Math.sin(i)*0.1), h*(0.5+Math.cos(i*2)*0.2), w*0.02,0,Math.PI*2); ctx.fill(); function drawRainbow(ctx,w,h) for(let i=0;i<6;i++) ctx.fillStyle=`hsl($30+i*15, 80%, 65%)`; ctx.fillRect(w*0.2, h*0.55 + i*12, w*0.6, 8); function drawClouds(ctx,w,h) ctx.fillStyle='#F0F8FF'; ctx.beginPath(); ctx.ellipse(w*0.3,h*0.7,w*0.12,w*0.08,0,0,Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.ellipse(w*0.45,h*0.68,w*0.1,w*0.07,0,0,Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.ellipse(w*0.6,h*0.72,w*0.13,w*0.09,0,0,Math.PI*2); ctx.fill(); function drawGalaxy(ctx,w,h) for(let s=0;s<60;s++) ctx.fillStyle=`rgba(180,130,255,$Math.random()*0.6)`; ctx.fillRect(w*0.65+Math.random()*80, h*0.4+Math.random()*80, 2,2); ctx.fillStyle='#c7aaff'; ctx.beginPath(); ctx.ellipse(w*0.75,h*0.65,w*0.08,w*0.04,0,0,Math.PI*2); ctx.fill(); function drawTeaParty(ctx,w,h) ctx.fillStyle='#d9b48b'; ctx.fillRect(w*0.55,h*0.6,w*0.12,w*0.1); ctx.fillStyle='#f3e3c2'; ctx.beginPath(); ctx.ellipse(w*0.61,h*0.58,w*0.07,w*0.04,0,0,Math.PI*2); ctx.fill(); ctx.fillStyle='#a57c54'; ctx.fillRect(w*0.6,h*0.7,3,12); function drawMoonCrater(ctx,w,h) ctx.fillStyle='#cbc1a4'; ctx.beginPath(); ctx.arc(w*0.7, h*0.6, w*0.1,0,Math.PI*2); ctx.fill(); ctx.fillStyle='#a59173'; ctx.beginPath(); ctx.ellipse(w*0.72, h*0.58, w*0.03, w*0.02,0,0,Math.PI*2); ctx.fill(); function drawConstellation(ctx,w,h) ctx.beginPath(); for(let i=0;i<5;i++) let x = w*(0.6+Math.sin(i)*0.08); let y = h*(0.5+Math.cos(i*2)*0.08); ctx.fillStyle='#ffd966'; ctx.arc(x,y,4,0,Math.PI*2); ctx.fill(); ctx.fillStyle='gold'; ctx.fill(); if(i>0) ctx.fillRect(x-2,y-2,4,4); function drawShootingStar(ctx,w,h) ctx.fillStyle='#FFE484'; ctx.beginPath(); ctx.moveTo(w*0.8,h*0.3); ctx.lineTo(w*0.83,h*0.25); ctx.lineTo(w*0.75,h*0.28); ctx.fill(); ctx.fillStyle='white'; for(let i=0;i<8;i++) ctx.fillRect(w*0.7+Math.random()*40, h*0.25+Math.random()*30, 2,2); function drawNebula(ctx,w,h) ctx.globalAlpha=0.5; for(let i=0;i<40;i++) ctx.fillStyle=`hsl($280+Math.random()*40, 80%, 70%)`; ctx.beginPath(); ctx.arc(w*(0.65+Math.random()*0.3), h*(0.5+Math.random()*0.3), Math.random()*8,0,Math.PI*2); ctx.fill(); ctx.globalAlpha=1; function drawHomecoming(ctx,w,h) ctx.fillStyle='#78b57e'; ctx.fillRect(w*0.2,h*0.7,w*0.6,15); ctx.fillStyle='#6c9e6e'; ctx.beginPath(); ctx.rect(w*0.35,h*0.5,w*0.1,w*0.2); ctx.fill(); ctx.fillStyle='#b57c48'; ctx.beginPath(); ctx.moveTo(w*0.32,h*0.5); ctx.lineTo(w*0.4,h*0.42); ctx.lineTo(w*0.48,h*0.5); ctx.fill();
takes a unique approach. It calculates the page flip effect based on mouse cursor position, creating a highly realistic and interactive response to user input. This mathematical approach allows for a dynamic flipping experience that feels more responsive to touch and mouse movements, mimicking the natural physics of a real book. A standard approach involves a master wrapper containing
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;
Welcome to the Book This is the back of page 1 This is page 2 The End Use code with caution. The CSS Styles
: This library provides an amazing flipbook component with animated pages using a canvas. It supports images and PDFs through pdfjs-dist and is incredibly tiny at only 18kb, which is 1000 times smaller than a competing library.
function prevPage() if(currentPage > 1) currentPage--; updateFlipbook();
// 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;