For the weekly #codepenchallenge, create a carousel based on grid properties and efficiently rendered by React components.
A Pen by Andreas Borgen on CodePen.
For the weekly #codepenchallenge, create a carousel based on grid properties and efficiently rendered by React components.
A Pen by Andreas Borgen on CodePen.
.carousel(data-carousel-loop) | |
nav.nav | |
a.prev Prev | |
a.next Next | |
dl.slides | |
div | |
dt #[del Grid Based] #[ins Flexbox] Carousel | |
dd How to? Here's the gist. | |
dd.code P.S. Feel free to tap around | |
div | |
dt For starters | |
dd Create a grid out of a wrapping container. | |
dd.code display: grid; | |
div | |
dt Moving on | |
dd Detail a single column which spans to cover the entirety of the container. | |
dd.code grid-template-columns: 100%; | |
div | |
dt Plot twist! | |
dd Specify how additional grid items should be added in new columns instead of new rows. | |
dd.code grid-auto-flow: column; | |
div | |
dt Almost there | |
dd Detail the width of the implicit columns to match the width of the only explicit column. | |
dd.code grid-auto-columns: 100%; | |
div | |
dt Finishing up | |
dd Hide horizontal overflow on the wrapping container. | |
dd.code overflow-x: hidden; | |
div | |
dt And that is all | |
dd Okay, there is plenty more behind this pen... The entire project is based on the mentioned grid properties though. | |
dd |
(function() { | |
function $(selector, context) { | |
return (context || document).querySelector(selector); | |
} | |
function $$(selector, context) { | |
return [].slice.call( (context || document).querySelectorAll(selector) ); | |
} | |
function addEvent(target, type, handler) { | |
target.addEventListener(type, handler, false); | |
} | |
function initCarousel(caro) { | |
const slides = $('.slides', caro), | |
prev = $('.prev', caro), | |
next = $('.next', caro), | |
loop = caro.hasAttribute('data-carousel-loop'); | |
let current = 0; | |
addEvent(prev, 'click', handleButton); | |
addEvent(next, 'click', handleButton); | |
function handleButton(e) { | |
// prevent bubbling up of the click event | |
// otherwise the click will be heard by the card as well and trigger a second handleUpdate() | |
e.stopPropagation(); | |
// handle the update based on current slide and direction | |
handleUpdate(e.currentTarget === next); | |
} | |
addEvent(caro, 'click', handleCard); | |
function handleCard(e) { | |
// similar to handleButton(), but check whether the click is heard on the left/right half of the slide | |
const bounds = caro.getBoundingClientRect(), | |
rightSide = (e.clientX > (bounds.left + bounds.width/2)); | |
handleUpdate(rightSide); | |
} | |
function handleUpdate(goNext) { | |
const slidesCount = slides.childElementCount; | |
current += goNext ? 1 : -1; | |
if(loop) { | |
current = current % slidesCount; | |
while(current < 0) { current += slidesCount; } | |
} | |
else { | |
current = Math.max(0, Math.min(current, slidesCount-1)); | |
} | |
//console.log(current); | |
slides.style.transform = `translateX(${current * -100}%)`; | |
} | |
} | |
$$('.carousel').forEach(initCarousel); | |
})(); |
@import url('https://fonts.googleapis.com/css?family=Fira+Mono|Montserrat'); | |
/* Default carousel style */ | |
.carousel { | |
position: relative; | |
width: 40em; | |
height: 25em; | |
max-width: 80%; | |
max-height: 80vh; | |
background: white; | |
box-shadow: 0 0 4px 0 gray; | |
//Hide horizontal overflow, effectively hiding all columns except one | |
overflow-x: hidden; | |
/* Essential properties for the carousel */ | |
.slides { | |
/* | |
//Position the sections in a grid | |
display: grid; | |
height: 100%; | |
//Include a single column layout, with implicit tracks added horizontally instead of vertically | |
grid-template-columns: 100%; | |
//Have the columns, implicit or explcit, spread to cover 100% of the container | |
grid-auto-flow: column; | |
grid-auto-columns: 100%; | |
//*/ | |
//* Works in IE | |
display: flex; | |
height: 100%; | |
flex-flow: row nowrap; | |
> * { | |
flex: 0 0 100%; | |
} | |
//*/ | |
//We'll use translateX() to flip through the carousel | |
transition: transform .5s; | |
} | |
.nav { | |
display: flex; | |
position: absolute; | |
left:.5em; bottom:.5em; | |
z-index: 1; | |
opacity: .5; | |
transition: opacity .3s; | |
.prev, .next { | |
position: relative; | |
display: inline-block; | |
height: 2em; | |
width: 2em; | |
overflow: hidden; | |
cursor: pointer; | |
&::after { | |
content: '❮'; | |
display: block; | |
position: absolute; | |
top:0;left:0;bottom:0;right:0; | |
background: black; | |
color: white; | |
font-size: 2em; | |
text-align: center; | |
line-height: 1; | |
} | |
} | |
.next { | |
margin-left: .25em; | |
&::after { content: '❯'; } | |
} | |
} | |
&:hover nav { | |
opacity: 1; | |
} | |
} | |
/* Page-specific styles */ | |
body { | |
display: flex; | |
height: 100vh; | |
margin: 0; | |
justify-content: center; | |
align-items: center; | |
font-family: Montserrat, sans-serif; | |
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><circle cx="90%" cy="80%" r="15%" fill="%230041F3"/><circle cx="0" cy="0" r="40%" fill="%23EE0034"/></svg>') center/contain no-repeat; | |
} | |
dl { | |
margin: 0; | |
> div { | |
display: flex; | |
flex-flow: column nowrap; | |
justify-content: space-around; | |
align-items: center; | |
font-size: 2em; | |
} | |
dt { | |
font-weight: bold; | |
text-transform: uppercase; | |
transform: rotate(-5deg); | |
} | |
dd { | |
margin: 0 1em; | |
text-align: center; | |
//IE.. | |
max-width: 100%; | |
&.code { | |
padding: .2em .57em; | |
font-family: monospace; | |
font-size: .8em; | |
background: black; | |
color: gainsboro; | |
} | |
} | |
} |