HTML
<h1>Turning pages with CSS
by Amit Sheen codepen
</h1>
<div class="imgLoader"></div>
<div class="container">
<h1 class="title">
Turning pages<br>with css
</h1>
<div class="credit">
* Images loaded randomly from Picsum.photos
</div>
<div class="book">
<div class="gap"></div>
<div class="pages">
<div class="page"></div>
<div class="page"></div>
<div class="page"></div>
<div class="page"></div>
<div class="page"></div>
<div class="page"></div>
</div>
<div class="flips">
<div class="flip flip1">
<div class="flip flip2">
<div class="flip flip3">
<div class="flip flip4">
<div class="flip flip5">
<div class="flip flip6">
<div class="flip flip7"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<a href="https://twitter.com/amit_sheen" class="twitterLink" target="_blank">
<img src="https://assets.codepen.io/1948355/twitterLogo2.png" />
</a>
SCSS
@import url('https://fonts.googleapis.com/css2?family=Indie+Flower&display=swap');
* {
padding: 0;
margin: 0 auto;
box-sizing: border-box;
}
body {
font-family: 'Indie Flower', cursive;
background-color: #eee;
color: #555;
text-align: center;
padding: 4em 0;
}
$bookAngle: 60deg;
$speed: 5s;
$borderColor: #555;
$images:
url('https://picsum.photos/420/300?random=1'),
url('https://picsum.photos/420/300?random=2'),
url('https://picsum.photos/420/300?random=3'),
url('https://picsum.photos/420/300?random=4'),
url('https://picsum.photos/420/300?random=5'),
url('https://picsum.photos/420/300?random=1');
// Preload to images
.imgLoader {
position: fixed;
animation: preLoad 1s steps(1);
width: 1px;
height: 1px;
@keyframes preLoad {
@for $i from 0 through 4 {
#{$i * 10}% { background-image: nth($images, ($i + 1)); }
}
100% { display: none; }
}
}
.container {
position: relative;
width: 420px;
border: #fff solid 2px;
border-radius: 4px;
height: 420px;
}
.title {
position: absolute;
top: 45px; left: 0;
width: 100%;
font-size: 2em;
font-weight: normal;
line-height: 1;
}
.credit {
position: absolute;
top: 100%; left: 0;
font-size: 0.9em;
text-align: left;
}
.book {
position: relative;
perspective: 630px;
perspective-origin: center 50px;
transform: scale(1.2);
filter: drop-shadow(0px 10px 5px rgba(0,0,0,0.25));
}
.page {
width: 210px;
height: 300px;
background-color: #bbb;
position: absolute;
top: 0px; right: 50%;
transform-origin: 100% 100%;
border: solid $borderColor 2px;
background-size: 420px 300px;
background-position: center;
&:nth-child(1) {transform: rotateX($bookAngle) rotateY(3deg); }
&:nth-child(2) { transform: rotateX($bookAngle) rotateY(4.5deg); }
&:nth-child(3) {
transform: rotateX($bookAngle) rotateY(6deg);
animation: nextPage $speed*5 infinite $speed*-4.8 steps(1);
background-size: 420px 300px;
background-position: -2px -2px;
}
&:nth-child(4) { transform: rotateX($bookAngle) rotateY(177deg); }
&:nth-child(5) { transform: rotateX($bookAngle) rotateY(175.5deg); }
&:nth-child(6) {
transform: rotateX($bookAngle) rotateY(174deg);
overflow: hidden;
&::after {
content: '';
width: 210px;
height: 300px;
position: absolute;
top: 0px; right: 0%;
transform-origin: center;
transform: rotateY(180deg);
animation: nextPage $speed*5 $speed*-4 infinite steps(1);
background-size: 420px 300px;
background-position: 100% -2px;
}
}
@keyframes nextPage {
@for $i from 0 through 4 {
#{$i * 20}% { background-image: nth($images, ($i + 1)); }
}
}
}
.gap {
width: 10px;
height: 300px;
background: none;
transform: rotateX($bookAngle);
transform-origin: bottom;
position: absolute;
top: 0px; left: calc(50% - 5px);
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, 50%);
background-color: $borderColor;
width: 10px;
height: 5px;
border-radius: 50%;
}
}
.flip {
width: 32px;
height: 300px;
position: absolute;
top: 0px;
transform-origin: 100% 100%;
right: 100%;
border: solid $borderColor;
border-width: 2px 0px;
perspective: 4200px;
perspective-origin: center;
transform-style: preserve-3d;
background-size: 420px 300px;
&::after {
content: '';
position: absolute;
top: 0px; right: 0%;
width: 100%; height: 100%;
transform-origin: center;
background-size: 420px 300px;
}
&.flip1 {
right: 50%;
animation: flip1 $speed infinite ease-in-out;
border-width: 2px 2px 2px 0;
&::after {
animation: nextFlip1 $speed*5 $speed*-4 infinite steps(1);
}
}
&:not(.flip1) {
right: calc(100% - 2px);
top: -2px;
transform-origin: right;
animation: flip2 $speed ease-in-out infinite;
}
@for $i from 2 through 7 {
&.flip#{$i}::after { animation: nextFlip#{$i} $speed*5 $speed*-4 infinite steps(1); }
}
&.flip7 {
width: 30px;
border-width: 2px 0px 2px 2px;
&::after { animation: nextFlip7 $speed*5 $speed*-4 infinite steps(1); }
}
@keyframes flip1 {
0%, 20% { transform: rotateX($bookAngle) rotateY(6deg); }
80%, 100% { transform: rotateX($bookAngle) rotateY(174deg); }
}
@keyframes flip2 {
0%, 20% { transform: rotateY(0deg) translateY(0px); }
50% { transform: rotateY(-15deg) translateY(0px); }
}
}
@keyframes nextFlip1 {
@for $i from 0 through 4 {
#{$i * 20}% { background-image: nth($images, ($i + 1)); background-position: -178px -2px; transform: rotateY(0deg); }
#{10 + ($i * 20)}% { background-image: nth($images, ($i + 2)); background-position: -210px -2px; transform: rotateY(180deg); }
}
}
@for $i from 2 through 6 {
@keyframes nextFlip#{$i} {
@for $j from 0 through 4 {
#{$j * 20}% { background-image: nth($images, ($j + 1)); background-position: #{-148 + (($i - 2) * 30)}px -2px; transform: rotateY(0deg); }
#{((10 + ($j * 20)) + (($i - 1) * 0.5))}% { background-image: nth($images, ($j + 2)); background-position: #{-238 - (($i - 2) * 30)}px -2px; transform: rotateY(180deg); }
}
}
}
@keyframes nextFlip7 {
@for $i from 0 through 4 {
#{$i * 20}% { background-image: nth($images, ($i + 1)); background-position: -2px -2px; transform: rotateY(0deg); }
#{13 + ($i * 20)}% { background-image: nth($images, ($i + 2)); background-position: -388px -2px; transform: rotateY(180deg); }
}
}
.twitterLink {
position: fixed;
bottom: 0.5em; right: 0.5em;
& img {
width: 2em;
filter: grayscale(100%);
transition: filter 0.25s;
&:hover {
filter: grayscale(0%);
}
}
}