3D Book Cover
A book stands at an angle, showing the cream edges of its pages. Point at it and it turns to face you as the cover swings open on its hinge, revealing the first page inside. On a phone, tap it.
A field guideThe Art of Motion
Chapter oneEasing in
How to use it
- Paste the snippet just before
</head>. - Add
data-book-coverto a box with a width and a book’s shape, e.g.aspect-ratio: 2 / 3. - Its first child is the cover; an optional second child is the first page.
Options
Add any of these attributes to the same element.
data-trigger | hover (default; a tap on touch screens) or click. |
data-autoplay | Milliseconds: open and close on a timer until someone interacts. |
data-angle | How far the cover opens, in degrees. Default 140. |
data-thickness | Thickness of the book in px. Default 7% of its width. |
data-back | Colour of the back cover and the inside of the front. Default #2e1065. |
data-duration | Time to open or close. Default 1000 ms. |
The open book slides right to stay centred, so leave it room: about 1.8 times its width.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// 3D Book Cover: a book stands at an angle, showing the edges of its
// pages. Point at it and it turns to face you as the cover swings open on
// its hinge, revealing the first page. A tap opens it on touch screens.
// Usage:
// <div data-book-cover style="width: 180px; aspect-ratio: 2 / 3">
// <div class="cover">…</div>
// <div class="first-page">…</div> <!-- optional -->
// </div>
// Without a second child the first page is left blank, with faint lines.
// Optional: data-trigger ("hover", default, or "click"), data-autoplay (ms:
// open and close on a timer until someone interacts), data-angle (how far
// the cover opens, degrees, default 140), data-thickness (px, default 7%
// of the width), data-back (colour of the back cover and the inside of the
// front, default #2e1065), data-duration (ms, default 1000).
var DEFAULTS = { angle: 140, back: '#2e1065', duration: 1000 };
var css =
'.bkc{position:relative;perspective:1100px;-webkit-tap-highlight-color:transparent}' +
'.bkc-book{position:absolute;inset:0;transform-style:preserve-3d}' +
'.bkc-book>*{position:absolute;box-sizing:border-box}' +
'.bkc-back{inset:0;border-radius:2px 6px 6px 2px;box-shadow:0 18px 40px -10px rgba(30,10,60,.45)}' +
'.bkc-edge{top:1.5%;bottom:1.5%;right:1.5%;transform-origin:100% 50%;transform:rotateY(-90deg);' +
'background:repeating-linear-gradient(90deg,#f4f1ea 0 2px,#ddd6c8 2px 3px)}' +
'.bkc-top{left:0;right:1.5%;top:1.5%;transform-origin:50% 0;transform:rotateX(-90deg);background:repeating-linear-gradient(0deg,#f4f1ea 0 2px,#ddd6c8 2px 3px)}' +
'.bkc-page{top:1.5%;bottom:1.5%;left:0;right:1.5%;background:#fffdf8;overflow:hidden;' +
'background-image:repeating-linear-gradient(180deg,transparent 0 13px,rgba(120,100,160,.13) 13px 14px);background-position:0 18%}' +
'.bkc-page::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(30,10,60,.35),rgba(30,10,60,0) 40%);opacity:var(--bkc-shade,1);pointer-events:none}' +
'.bkc-cover{inset:0;transform-origin:0 50%;transform-style:preserve-3d}' +
'.bkc-cover>*{position:absolute;inset:0;box-sizing:border-box;backface-visibility:hidden;-webkit-backface-visibility:hidden;border-radius:2px 6px 6px 2px;overflow:hidden}' +
'.bkc-inside{transform:rotateY(180deg)}' +
'.bkc-inside::after{content:"";position:absolute;inset:0;background:linear-gradient(270deg,rgba(0,0,0,.35),transparent 50%)}' +
'.bkc-front::after{content:"";position:absolute;top:0;bottom:0;left:4%;width:2px;background:rgba(0,0,0,.18);box-shadow:1px 0 0 rgba(255,255,255,.18)}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
function part(cls, parent) {
var p = document.createElement('div');
p.className = cls;
parent.appendChild(p);
return p;
}
function setup(el) {
var d = el.dataset;
var kids = Array.from(el.children);
if (!kids.length) return;
el.classList.add('bkc');
var back = d.back || DEFAULTS.back;
var book = part('bkc-book', el);
part('bkc-back', book).style.background = back;
var edge = part('bkc-edge', book);
var top = part('bkc-top', book);
var page = part('bkc-page', book);
if (kids[1]) page.appendChild(kids[1]);
var cover = part('bkc-cover', book);
kids[0].classList.add('bkc-front');
cover.appendChild(kids[0]);
part('bkc-inside', cover).style.background = back;
edge.setAttribute('aria-hidden', 'true');
top.setAttribute('aria-hidden', 'true');
var angle = d.angle !== undefined ? +d.angle : DEFAULTS.angle;
var duration = d.duration !== undefined ? +d.duration : DEFAULTS.duration;
var s = el._bkc = { p: 0, open: false, anim: null, timer: null };
var w = 0, t = 0;
function size() {
w = el.clientWidth;
t = +d.thickness || Math.max(6, Math.round(w * 0.07));
edge.style.width = t + 'px';
top.style.height = t + 'px';
page.style.transform = 'translateZ(-1px)';
book.firstChild.style.transform = 'translateZ(' + -t + 'px)';
draw();
}
function draw() {
var p = s.p;
// Slide right as it opens, so the open book stays centred.
var shift = Math.max(0, -Math.cos(angle * Math.PI / 180)) * w / 2 * p;
book.style.transform = 'translateX(' + shift + 'px) rotateX(' + -6 * (1 - p) + 'deg) rotateY(' + (-30 + 22 * p) + 'deg)';
cover.style.transform = 'rotateY(' + -angle * p + 'deg)';
page.style.setProperty('--bkc-shade', String(1 - p * 0.7));
}
function go(open) {
if (open === s.open) return;
s.open = open;
el.setAttribute('aria-expanded', open);
page.setAttribute('aria-hidden', !open);
if (s.anim) s.anim.pause();
s.anim = anime.animate(s, {
p: open ? 1 : 0,
duration: reduced() ? 0 : duration,
ease: open ? 'out(3)' : 'inOut(3)',
onUpdate: draw
});
}
page.setAttribute('aria-hidden', 'true');
size();
if (window.ResizeObserver) new ResizeObserver(size).observe(el);
function stop() { if (s.timer) { clearInterval(s.timer); s.timer = null; } }
if (d.trigger === 'click') {
el.style.cursor = 'pointer';
el.setAttribute('role', 'button');
el.setAttribute('aria-expanded', 'false');
if (!el.hasAttribute('tabindex')) el.tabIndex = 0;
el.addEventListener('click', function () { stop(); go(!s.open); });
el.addEventListener('keydown', function (e) {
if (e.key !== 'Enter' && e.key !== ' ') return;
e.preventDefault();
stop();
go(!s.open);
});
} else {
var touch = false;
el.addEventListener('pointerdown', function (e) { touch = e.pointerType !== 'mouse'; });
el.addEventListener('pointerenter', function (e) { if (e.pointerType === 'mouse') { stop(); go(true); } });
el.addEventListener('pointerleave', function (e) { if (e.pointerType === 'mouse') go(false); });
el.addEventListener('focusin', function () { go(true); });
el.addEventListener('focusout', function () { go(false); });
// Touch screens have no hover: a tap opens or closes it instead.
el.addEventListener('click', function () { if (touch) { stop(); go(!s.open); } });
}
if (d.autoplay !== undefined && d.autoplay !== 'false' && !reduced()) {
var visible = false;
new IntersectionObserver(function (entries) { visible = entries[0].isIntersecting; }).observe(el);
s.timer = setInterval(function () { if (visible) go(!s.open); }, +d.autoplay || 2600);
}
}
function init() {
document.querySelectorAll('[data-book-cover]').forEach(function (el) {
if (el._bkc) return;
setup(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div data-book-cover style="width: 180px; aspect-ratio: 2 / 3">
<div class="cover">The Art of Motion</div>
<div class="first-page">Chapter one</div>
</div>