Det vi skal lage ser slik ut:
Når en går litt i dybden på problemstillinger av denne typen så er det etn problemstilling som
raskt melder seg: Hvor mye skal vi gjøre i CSS og hvor mye skal vi gjøre i Javascript ?
Løsningen som er beskrevet nedenfor er antagelig mer JS-basert enn mange av tilsvarende prosjekter
du kan finne på nettet. Som vi har sett i andre moduler i dette materialet er det nesten
ingen grenser for hva vi kan få til av dynamikk i CSS. Jeg har valgt å
bruke en del Javascript fordi det gir meg en bedre følelse av kontroll.
Plan
Vi definerer en "scene" der vi til enhver tid plasserer de aktive sidene og de neste sidene,
dvs. de som ligger bak de aktive. page-elementene er i utganspunktet usynlige (display:none),
og innholdet må flyttes til scenen for å bli synlig.
Synlige sider
Koden som plasserer sidene på riktig plass blir slik:
//--------------------------------------
// set up according to given page index in parameter
// called from setUp after an animation
function prepareBook(pageIx){
curPageLeft=pageIx;
// if first or last page: override page styling
markNopages();
// set active
movePageTo(aLeft,curPageLeft);
movePageTo(aRight,curPageLeft+1);
// set behind, next
movePageTo(nLeft,curPageLeft-2);
movePageTo(nRight,curPageLeft+3);
}
Når vi første har gjort dette kan fokusere på det innholdet som er plassert i de fire feltene, de to aktive og de to som ligger bak.
CSS eller Javascript
Når vi skal flipp en side har vi mange valgmuligheter. Vi må hele tiden passe på
at siden alltid under vendingen viser riktig front. Det vil si at
innholdet må speiles når vi har rotert 90 grader. Dette lar seg løse med CSS (keyframe og animation), men jeg har valgt
å bruke Javascript til å kontrollere hele "flippen".
Koden som gjør en vending er slik:
//------------------------------
// clicked on left active page
function flipForwards()
{
if( animating ||(curPageLeft==0))
return;
clearInterval(rotator);
animating=true;
activePageHolder=document.getElementById("activeLeft");
rotator=setInterval("rotateRight()",stepTime);
degrees=0;
activePageHolder.setAttribute("style","z-index:500;");
}
function rotateRight()
{
degrees=degrees+dStep;
doTransform(activePageHolder,degrees,-50);
if(degrees==90)
{
// flip it
movePageTo(activePageHolder,curPageLeft-1);
activePageHolder.querySelector(".content").classList.add("flipped");
}
if (degrees==180 || degrees>=360){
clearInterval(rotator);
animating=false;
activePageHolder.querySelector(".content").classList.remove("flipped");
activePageHolder.setAttribute("style","");
prepareBook(curPageLeft-2);
}
}
Funksjonen doTransform() er den som faktisk foretat et rotasjonssteg.
Den er laget slik at den bruker style attributten til å overkjøre classe definisjonen mens
flipp operasjonen foregår. Dette er en ganske hendig teknikk og fungerer fordi style har høyere
prioritet enn class. Vi må bare huske på å fjerne slike midlertidige effekter. Funksjonen er slik:
//---------------------------------
// perform a rotation, will only affect style element
function doTransform(elt,rotY,transX){
elt.style.transform="rotateY(" + rotY + "deg) translateX(" + transX+"%)";
elt.style.webkitTransform="rotateY(" + rotY + "deg) translateX(" + transX+"%)";
elt.style.OTransform="rotateY(" + rotY + "deg) translateX(" + transX+"%)";
elt.style.MozTransform="rotateY(" + rotY + "deg) translateX(" + transX+"%)";
}
All javascript koden er samlet slik:
// list of all pages
var pages;
// right or left active page
var activePageHolder;
// selected left page
var curPageLeft; // right page is allways +1
// active and next elements
var aLeft;
var aRight;
var nLeft;
var nRight;
// timer id, flag and current rotation during animation
var rotator;
var animating;
var degrees=0;
// flipping
// degrees pr step
var dStep=2; // condition: 90 % dStep==0
// time between drawings in flip in ms
var stepTime=10;
//----------------------------------
// called on loaded and from reset
function setUp(pageIx){
pages=document.querySelectorAll(".page");
//console.log(pages.length);
aLeft=document.getElementById("activeLeft");
aRight=document.getElementById("activeRight");
nLeft=document.getElementById("nextLeft");
nRight=document.getElementById("nextRight");
prepareBook(pageIx);
animating=false;
}
//------------------------------------
// go to start
// called from click on last (no)page
function reset(evt){
setUp(0);
event = evt || window.event;
if (event.stopPropagation) {
event.stopPropagation()
} else {
// IE variant
event.cancelBubble = true
}
return false;
}
//prep
//--------------------------------------
// set up according to given page index in parameter
// called from setUp after an animation
function prepareBook(pageIx){
curPageLeft=pageIx;
// if first or last page: override page styling
markNopages();
// set active
movePageTo(aLeft,curPageLeft);
movePageTo(aRight,curPageLeft+1);
// set behind, next
movePageTo(nLeft,curPageLeft-2);
movePageTo(nRight,curPageLeft+3);
}
//eofprep
//----------------------------------
// first and last page is visually no page
// should appear as part of table/background(scene)
function markNopages(){
var bC="#eee";// same as body
// use style attribute to supress class values
// first page is no page
if(curPageLeft==0){
aLeft.style.backgroundColor=bC;
aLeft.style.border="none";
}
else
aLeft.setAttribute("style","");
if(curPageLeft==2){
nLeft.style.backgroundColor=bC;
nLeft.style.border="none";
}
else
nLeft.setAttribute("style","");
// last page is also no page
if(curPageLeft==pages.length-2){
aRight.style.backgroundColor=bC;
aRight.style.border="none";
}
else
aRight.setAttribute("style","");
if(curPageLeft==pages.length-4){
nRight.style.backgroundColor=bC;
nRight.style.border="none";
}
else
nRight.setAttribute("style","");
}
//------------------------------------
// controlling and moving page content to active or next
function legalPage(ix){
return ix < pages.length && ix >=0;
}
function movePageTo(target,pageIx){
if(legalPage(pageIx))
target.innerHTML=pages[pageIx].innerHTML;
else
target.innerHTML='<div class="content">X</div>'
}
//dotrans
//---------------------------------
// perform a rotation, will only affect style element
function doTransform(elt,rotY,transX){
elt.style.transform="rotateY(" + rotY + "deg) translateX(" + transX+"%)";
elt.style.webkitTransform="rotateY(" + rotY + "deg) translateX(" + transX+"%)";
elt.style.OTransform="rotateY(" + rotY + "deg) translateX(" + transX+"%)";
elt.style.MozTransform="rotateY(" + rotY + "deg) translateX(" + transX+"%)";
}
//eofdotrans
//flipfwd
//------------------------------
// clicked on left active page
function flipForwards()
{
if( animating ||(curPageLeft==0))
return;
clearInterval(rotator);
animating=true;
activePageHolder=document.getElementById("activeLeft");
rotator=setInterval("rotateRight()",stepTime);
degrees=0;
activePageHolder.setAttribute("style","z-index:500;");
}
function rotateRight()
{
degrees=degrees+dStep;
doTransform(activePageHolder,degrees,-50);
if(degrees==90)
{
// flip it
movePageTo(activePageHolder,curPageLeft-1);
activePageHolder.querySelector(".content").classList.add("flipped");
}
if (degrees==180 || degrees>=360){
clearInterval(rotator);
animating=false;
activePageHolder.querySelector(".content").classList.remove("flipped");
activePageHolder.setAttribute("style","");
prepareBook(curPageLeft-2);
}
}
//eofflipfwd
//--------------------------
// clicked on right active page
function flipBackwards()
{
if(animating || (curPageLeft>pages.length-3))
return;
clearInterval(rotator);
animating=true;
activePageHolder=document.getElementById("activeRight");
rotator=setInterval("rotateLeft()",stepTime);
degrees=360;
activePageHolder.setAttribute("style","z-index:500;");
}
function rotateLeft()
{
degrees=degrees-dStep;
doTransform(activePageHolder,degrees,50);
if(degrees==270)
{
movePageTo(activePageHolder,curPageLeft+2);
activePageHolder.querySelector(".content").classList.add("flipped");
}
if (degrees==180 || degrees>=360){
clearInterval(rotator);
animating=false;
activePageHolder.querySelector(".content").classList.remove("flipped");
activePageHolder.setAttribute("style","");
prepareBook(curPageLeft+2);
}
}
Stilsettet er slik:
*,
*:before,
*:after {
-moz-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
max-width: 100% !important;
overflow-x: hidden !important;
font-size: 62.5%;
font-size: 1.2rem;
height: 100%;
overflow: hidden;
background: #eee;}
@media (max-width: 767px) {
html,
body { font-size: 1rem; }
}
@media (max-width: 640px) {
html,
body { font-size: 0.9rem; }
}
@media (max-width: 480px) {
html,
body { font-size: 0.9rem; }
}
.scene{
position:absolute;
left:10px;
top:0.1rem;
width:90%;
height:95%;
perspective: 5000px;
-webkit-perspective: 5000px;
}
.active ,.nextactive {
transform-style: preserve-3d;
color: #000;
position:absolute;
width:50%; height:95%;
padding: 10px;
border: 1px solid #888;
border-radius: 7px;
background: none repeat scroll 0% 0% #FFF;
margin: 10px 10px 0px 10px;
}
.active{z-index:100;
overflow:auto;
}
.nextactive {z-index:50;
overflow:hidden;
}
#activeLeft,#nextLeft{
top:1%;
left:25%;
transform: translateX(-50%);
-webkit-transform: translateX(-50%);
-ms-transform: translateX(-50%);
}
#activeRight,#nextRight{
top:1%;
left:25%;
transform: translateX(50%);
-webkit-transform: translateX(50%);
-ms-transform: translateX(50%);
}
.flipped{
transform:rotateY(180deg);
-webkit-transform:rotateY(180deg);
-O-transform:rotateY(180deg);
-ms-transform:rotateY(180deg);
}
.content{
position:relative;
display:block;
font-family: Decade;
}
.content img{
height:auto;
max-width:100%;
}
Forbedringer
Eksempelet har et stort forbedringspotensiale.
Noen som kanskje kunne gjøres uten alt for mye arbeid:
- Side angivelse. Vi burde kanskje sett på et sidenummer på sidene, f.eks opp i hjørnet.
- Side indikator. Når vi blar i boka har vi ikke noe inntrykk av hvor langt vi har kommet og hvor mye som er
"ulest". Vi kunne jo legge på noe slikt:
Vi kunne jo også gjøre den aktiv slik at vi kunne bla med den.
-
Side 0, den som ikke ser ut som en side, burde jo vært en bokhylle der vi kunne hente flere bøker,
f.eks. ved et AJAX kall. Det vi da måtte få tak i ville jo egentlig bare være et HTML-segment med page-elementer.
-
Det er nok en god del å hente i å raffinere det som er av responsive design. Du kan jo teste
demoen i utvikler modus i nettleseren, eller laste den opp i en eller annen mobil etter brett.
-
Det kan jo også være en interessant øvelse, og noen vil sikkert si forbedring, å overføre animasjonen til ren CSS.