Last active
September 12, 2017 07:39
-
-
Save branneman/5752a537f8c31011027891df566d0caf to your computer and use it in GitHub Desktop.
Example Carousel, made with https://github.com/mirabeau-nl/frontend-bootstrap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.carousel { | |
width: 100%; | |
* { | |
user-select: none; | |
} | |
&-window, | |
&-controls { | |
margin: 0; | |
padding-left: 0; | |
list-style-type: none; | |
} | |
&-window { | |
overflow-scrolling: touch; | |
-webkit-overflow-scrolling: touch; | |
white-space: nowrap; | |
overflow-x: scroll; | |
overflow-y: hidden; | |
max-width: rem(640px); | |
margin: 0 auto; | |
* { | |
position: relative; | |
display: inline-block; | |
width: 100%; | |
} | |
li { | |
background-color: white; | |
cursor: grab; | |
animation: hide-a-while $perfomance-budget; | |
&:active { | |
cursor: grabbing; | |
} | |
} | |
img { | |
display: block; | |
height: 0; | |
box-sizing: border-box; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
width: 100%; | |
padding-bottom: 75%; | |
background-size: contain; | |
} | |
@supports (display: flex) { | |
display: flex; | |
flex-wrap: nowrap; | |
> * { | |
flex: 0 0 100%; | |
} | |
} | |
} | |
&[data-initialized] &-window { | |
overflow: hidden; | |
li { | |
opacity: 0; | |
animation: none; | |
transition: transform $pace-slow $ease-out-quart, opacity $pace-slow; | |
} | |
li[data-active="true"] { | |
opacity: 1; | |
transition: transform $pace-slow $ease-out-quart, opacity $pace-fast; | |
} | |
li[data-active="true"] ~ li { | |
opacity: 0; | |
} | |
} | |
&-controls { | |
display: none; | |
} | |
&[data-initialized] &-controls { | |
display: flex; | |
justify-content: center; | |
margin: $spacing-l $spacing-s; | |
} | |
&-bullet { | |
position: relative; | |
flex: 0 1 rem(120px); | |
border: 1px solid $color-smoke; | |
margin-right: -1px; | |
cursor: pointer; | |
&:before { | |
content: ''; | |
display: block; | |
background-color: transparent; | |
width: 100%; | |
height: 100%; | |
position: absolute; | |
border-radius: 0; | |
transition: box-shadow $pace-normal; | |
} | |
&[data-selected='true']:before { | |
z-index: 2; | |
box-shadow: 0 5px 5px rgba(black, .19); | |
} | |
&-label { | |
@include screenreader; | |
} | |
img { | |
display: block; | |
height: 0; | |
box-sizing: border-box; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
width: 100%; | |
padding-bottom: 100%; | |
background-size: contain; | |
} | |
} | |
@media (min-width: $breakpoint-m) { | |
&[data-initialized] &-window { | |
margin-bottom: $spacing-xl; | |
li { | |
transition: transform $pace-slower $ease-out-quart, opacity $pace-slow; | |
} | |
li[data-active="true"] { | |
transition: transform $pace-slower $ease-out-quart, opacity $pace-fast; | |
} | |
} | |
&[data-initialized] &-controls { | |
margin: $spacing-l; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="carousel" | |
data-module="carousel/Carousel" | |
data-bullet=".carousel-bullet" | |
data-window=".carousel-window"> | |
<ul class="carousel-window"> | |
{% for slide in carousel.slides %} | |
<li><img id="{{ slide.id }}" src="{{ baseUri }}{{ slide.src }}" style="background-image: url({{ baseUri }}{{ slide.src }});" alt="{{ slide.label }}"></li> | |
{% endfor %} | |
</ul> | |
<ul class="carousel-controls" aria-hidden="true"> | |
{% for slide in carousel.slides %} | |
<li class="carousel-bullet"> | |
<a href="#{{ slide.id }}"> | |
<img src="{{ baseUri }}{{ slide.src }}" style="background-image: url({{ baseUri }}{{ slide.src }});" alt=""> | |
<span class="carousel-bullet-label">{{ slide.label }}</span> | |
</a> | |
</li> | |
{% endfor %} | |
</ul> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { raf } from 'utils/eventHelpers'; | |
class Carousel { | |
constructor(element, options) { | |
this._element = element; | |
this._options = Object.assign(Carousel.options, options); | |
this._refs = { | |
window: element.querySelector(this._options.window), | |
bullets: [].slice.call(element.querySelectorAll(this._options.bullet)), | |
slides: [] | |
}; | |
this._refs.slides = [].slice.call(this._refs.window.children); | |
this._refs.window.scrollLeft = 0; | |
this._element.setAttribute('data-initialized', 'true'); | |
this._onTouchMove = raf(this._onTouchMove.bind(this)); | |
this._refs.window.addEventListener('mousedown', e => this._onTouchStart(e), { passive: true }); | |
this._refs.window.addEventListener('touchstart', e => this._onTouchStart(e), { passive: true }); | |
this._refs.window.addEventListener('mousemove', e => this._onTouchMove(e), { passive: true }); | |
this._refs.window.addEventListener('touchmove', e => this._onTouchMove(e), { passive: true }); | |
this._refs.window.addEventListener('touchend', e => this._onTouchEnd(e), { passive: true }); | |
document.addEventListener('mouseup', e => this._onTouchEnd(e), { passive: true }); | |
this._refs.bullets.forEach(bullet => { | |
bullet.addEventListener('click', e => this._onBulletClick(e)); | |
bullet.addEventListener('touchend', e => this._onBulletClick(e)); | |
}); | |
this._refs.slides.forEach(slide => slide.addEventListener('dragstart', e => e.preventDefault())); | |
this._state = { | |
index: 0, | |
dragging: false, | |
touchStart: { | |
x: 0, | |
y: 0 | |
} | |
}; | |
this.goto(0); | |
} | |
static options = { | |
bullet: null, | |
window: null, | |
threshold: 30 | |
} | |
get slideWidth() { | |
return this._refs.slides[0].offsetWidth; | |
} | |
set activeBullet(index) { | |
this._refs.bullets.forEach((bullet, i) => { | |
bullet.setAttribute('data-selected', index === i); | |
}); | |
} | |
_onBulletClick(e) { | |
e.preventDefault(); | |
const index = this._refs.bullets.indexOf(e.target); | |
if (index !== -1) this.goto(index); | |
} | |
_onTouchStart(e) { | |
if (this._state.dragging) return; | |
this._state.dragging = true; | |
const pointer = e.changedTouches ? e.changedTouches[0] : e; | |
this._state.touchStart = { | |
x: pointer.pageX, | |
y: pointer.pageY | |
}; | |
} | |
_onTouchMove(e) { | |
if (!this._state.dragging) return; | |
const pointer = e.changedTouches ? e.changedTouches[0] : e; | |
const d = { | |
x: pointer.pageX - this._state.touchStart.x, | |
y: pointer.pageY - this._state.touchStart.y | |
}; | |
if (Math.abs(d.x) > Math.abs(d.y)) { | |
const offset = -this._state.index * this.slideWidth + d.x; | |
const isWithinBounds = offset >= this.slideWidth * -(this._refs.slides.length - 1) && offset <= 0; | |
const dir = offset / Math.abs(offset); | |
const target = isWithinBounds ? offset : offset - d.x * .66 + dir * Math.sqrt(Math.abs(d.x) * .66); | |
this._refs.slides.forEach(slide => { | |
slide.style.transform = `translateX(${ target }px`; | |
slide.style.transitionDuration = '.1s'; | |
}); | |
} else { | |
this.slideTo(this.index); | |
} | |
} | |
_onTouchEnd(e) { | |
if (!this._state.dragging) return; | |
this._state.dragging = false; | |
const pointer = e.changedTouches ? e.changedTouches[0] : e; | |
const d = { | |
x: pointer.pageX - this._state.touchStart.x, | |
y: pointer.pageY - this._state.touchStart.y | |
}; | |
if (Math.abs(d.x) > Math.abs(d.y) && Math.abs(d.x) > this._options.threshold) { | |
d.x > 0 ? this.previous() : this.next(); | |
} else { | |
this.slideTo(this._state.index); | |
} | |
} | |
goto(index) { | |
const max = this._refs.slides.length - 1; | |
index = Math.min(Math.max(index, 0), max); | |
this._refs.slides[this._state.index].removeAttribute('data-active'); | |
this._refs.slides[index].setAttribute('data-active', 'true'); | |
this.slideTo(index); | |
this.activeBullet = index; | |
this._state.index = index; | |
} | |
previous() { | |
this.goto(this._state.index - 1); | |
} | |
next() { | |
this.goto(this._state.index + 1); | |
} | |
slideTo(index) { | |
const target = -index * this.slideWidth; | |
this._refs.slides.forEach(slide => { | |
slide.style.transform = `translateX(${ target }px`; | |
slide.style.transition = ''; | |
}); | |
} | |
} | |
export default Carousel; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"carousel": { | |
"slides": [ | |
{ | |
"id": "slide-1", | |
"src": "/media/products/tefal-reisbeker/1.jpg", | |
"label": "Tefal Reisbeker" | |
}, | |
{ | |
"id": "slide-2", | |
"src": "/media/products/tefal-reisbeker/2.jpg", | |
"label": "Tefal Reisbeker" | |
}, | |
{ | |
"id": "slide-3", | |
"src": "/media/products/tefal-reisbeker/3.jpg", | |
"label": "Tefal Reisbeker" | |
}, | |
{ | |
"id": "slide-4", | |
"src": "/media/products/tefal-reisbeker/4.jpg", | |
"label": "Tefal Reisbeker" | |
} | |
] | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
title: Carousel |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment