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; | |
| } | |
| } | |
| } |