Last active
October 2, 2018 21:42
-
-
Save AquilaSands/d434af4f126427b29eafdd63d16b9bec to your computer and use it in GitHub Desktop.
Aurelia Carousel Gist
This file contains hidden or 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
| <template> | |
| <require from=./carousel></require> | |
| <h1>${message}</h1> | |
| <div> | |
| <carousel show-nav.bind="true" heading="Morgan Fillman" options.bind="options"> | |
| <img repeat.for="i of 5" src="https://morganfillman.space/400/300" > | |
| </carousel> | |
| </div> | |
| <div style="height:50px;"></div> | |
| <div> | |
| <carousel show-nav.bind="true" heading="Morgan Fillman" options.bind="options2"> | |
| <span repeat.for="i of 5">fsfsdfaffdfaf ${$index}</span> | |
| </carousel> | |
| </div> | |
| </template> |
This file contains hidden or 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
| export class App { | |
| message = 'Aurelia Carousel Test'; | |
| options = { | |
| duration: 600, | |
| perPage: { | |
| 640: 2, | |
| 768: 3, | |
| 1080: 4 | |
| }, | |
| draggable:true, | |
| loop: false, | |
| autoPlay: true, | |
| autoPlayInterval: 2500 | |
| }; | |
| options2 = { | |
| duration: 600, | |
| perPage: { | |
| 640: 2, | |
| 768: 3, | |
| 1080: 4 | |
| }, | |
| draggable:true, | |
| loop: false, | |
| autoPlay: true, | |
| autoPlayInterval: 2500 | |
| }; | |
| } |
This file contains hidden or 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
| .wrapper { | |
| position: relative; | |
| } | |
| .carousel { | |
| overflow: hidden; | |
| } | |
| .grab { | |
| cursor: -webkit-grab; | |
| cursor: grab; | |
| } | |
| .grabbing { | |
| cursor: -webkit-grabbing; | |
| cursor: grabbing; | |
| } | |
| .slider > * { | |
| float: left; | |
| } | |
| .heading { | |
| height: 6rem; | |
| color: #3ccaf8; | |
| position: relative; | |
| width: 100%; | |
| font-family: "Gotham Medium"; | |
| display: inline-block; | |
| padding: 0 10px; | |
| } | |
| .headingTitle { | |
| margin-bottom: .5rem; | |
| width: 100%; | |
| } | |
| .headingTitle h2 { | |
| font-size: 1.5rem; | |
| margin: 0; | |
| line-height: 1; | |
| letter-spacing: -.5px; | |
| font-weight: 500; | |
| display: inline-block; | |
| } | |
| .headingTitle span { | |
| font-size: .75rem; | |
| line-height: 1.25rem; | |
| display: block; | |
| } | |
| .controlsHead { | |
| top: 3rem; | |
| position: absolute; | |
| width: 100%; | |
| padding: 0 10px; | |
| } | |
| .controlsFoot { | |
| position: absolute; | |
| right: 0; | |
| bottom: 0; | |
| margin: 0 .6rem; | |
| } | |
| .nav { | |
| float: right; | |
| margin: 5px 0 15px 10px; | |
| } | |
| .navBtn { | |
| cursor: pointer; | |
| background: #3ccaf8; | |
| font-size: 1.875rem; | |
| color: #fff; | |
| text-indent: -1px; | |
| padding: 0 .8rem; | |
| box-shadow: 0 2px 3px rgba(0,0,0,.5); | |
| width: 2rem; | |
| display: inline-block; | |
| } | |
| .navBtn:active { | |
| box-shadow: inset 0 2px 3px rgba(0,0,0,.3); | |
| } | |
| .navBtnIcon { | |
| vertical-align: top; | |
| pointer-events: none; | |
| width: 0; | |
| height: 0; | |
| } | |
| @media screen and (min-width: 48em) { | |
| .heading { | |
| height: 60px; | |
| padding: 0; | |
| } | |
| .headingTitle { | |
| position: absolute; | |
| margin: 0; | |
| z-index: 1; | |
| } | |
| .headingTitle h2 { | |
| font-size: 1.875rem; | |
| background: #fff; | |
| padding: 0 .6rem; | |
| } | |
| .headingTitle span { | |
| font-size: .875rem; | |
| line-height: 1.5rem; | |
| padding: 0 .6rem; | |
| } | |
| .headingTitle::after{ | |
| content: ""; | |
| position: absolute; | |
| top: 20px; | |
| left: 10px; | |
| background: #cbc9c9; | |
| width: 90%; | |
| height: 1px; | |
| margin-top: 1px; | |
| z-index: -1; | |
| } | |
| .controlsHead { | |
| position: absolute; | |
| width: auto; | |
| right: 0; | |
| top: 0; | |
| z-index: 1; | |
| padding-left: .6rem; | |
| background: #fff; | |
| } | |
| } | |
| @media screen and (min-width: 64em) { | |
| .controlsHead { | |
| padding: 0 .6rem; | |
| } | |
| } |
This file contains hidden or 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
| <template> | |
| <require from="./carousel.css"></require> | |
| <div class="wrapper"> | |
| <!-- Header --> | |
| <div if.bind="heading" class="heading"> | |
| <!-- Heading --> | |
| <div if.bind="heading" class="headingTitle"> | |
| <h2>${heading}</h2> | |
| <span if.bind="sortBy">Sorted by ${sortBy} (${sortDir})</span> | |
| </div> | |
| </div> | |
| <!-- Carousel --> | |
| <div ref="carouselContainer" class="carousel ${config.draggable ? pointerDown ? 'grabbing': 'grab' : ''}" > | |
| <div ref="sliderFrame" class="slider" show.bind="initialized" css="width: ${sliderWidth}px; transition: transform ${transformDuration}ms ${config.easing}; ${transformProperty}: translate3d(${sliderTransform}px, 0, 0);;"> | |
| <slot></slot> | |
| </div> | |
| </div> | |
| <div class="${heading ? 'controlsHead' : 'controlsFoot'}"> | |
| <!-- Nav --> | |
| <div if.bind="showNav" class="nav"> | |
| <span click.delegate="prev()" class="navBtn ${navPrevDisabled ? 'navDisabled' : ''}"><i class="fa fa-angle-left navBtnIcon"></i></span> | |
| <span click.delegate="next()" class="navBtn ${navNextDisabled ? 'navDisabled' : ''}"><i class="fa fa-angle-right navBtnIcon"></i></span> | |
| </div> | |
| <!-- Optional filter buttons --> | |
| <slot name="buttons"></slot> | |
| </div> | |
| </div> | |
| </template> |
This file contains hidden or 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 { bindable, bindingMode } from 'aurelia-framework'; | |
| export class Carousel { | |
| @bindable showNav = true; | |
| @bindable heading = ''; | |
| @bindable({ defaultBindingMode: bindingMode.oneTime }) options; | |
| config = { | |
| duration: 200, | |
| easing: 'ease-out', | |
| perPage: 1, | |
| startIndex: 0, | |
| draggable: true, | |
| threshold: 20, | |
| loop: false, | |
| onInit: () => {}, | |
| onChange: () => {}, | |
| }; | |
| carouselContainer; | |
| sliderFrame; | |
| sliderWidth = 0; | |
| sliderTransform = 0; | |
| carouselWidth; | |
| innerElements; | |
| currentSlide; | |
| transformProperty; | |
| transformDuration = 0; | |
| viewPortWidth; | |
| resizeAnimRunning = false; | |
| slideAnimRunning = false; | |
| initialized = false; | |
| pointerDown = false; | |
| drag = { | |
| startX: 0, | |
| endX: 0, | |
| startY: 0, | |
| letItGo: null | |
| }; | |
| perPage; | |
| constructor() { | |
| } | |
| attached() { | |
| // Merge defaults with user's settings | |
| this.mergeSettings(); | |
| // Cache some stuff | |
| this.transformDuration = this.config.duration; | |
| this.currentSlide = this.config.startIndex; | |
| this.transformProperty = this.webkitOrNot(); | |
| this.viewPortWidth = window.innerWidth; | |
| this.carouselWidth = this.carouselContainer.offsetWidth; | |
| this.innerElements = this.sliderFrame.children; | |
| // Initialize the carousel | |
| this.init(); | |
| } | |
| detached () { | |
| window.removeEventListener('resize', this.resizeHandler); | |
| this.carouselContainer.removeEventListener('touchstart', this.touchstartHandler); | |
| this.carouselContainer.removeEventListener('touchend', this.touchendHandler); | |
| this.carouselContainer.removeEventListener('touchmove', this.touchmoveHandler); | |
| this.carouselContainer.removeEventListener('mousedown', this.mousedownHandler); | |
| this.carouselContainer.removeEventListener('mouseup', this.mouseupHandler); | |
| this.carouselContainer.removeEventListener('mouseleave', this.mouseleaveHandler); | |
| this.carouselContainer.removeEventListener('mousemove', this.mousemoveHandler); | |
| } | |
| /** | |
| * Overrides default settings with custom ones. | |
| * @param {Object} options - Optional settings object. | |
| * @returns {Object} - Custom Siema settings. | |
| */ | |
| mergeSettings = () => { | |
| for (const attrname in this.options) { | |
| this.config[attrname] = this.options[attrname]; | |
| } | |
| } | |
| /** | |
| * Determine if browser supports unprefixed transform property. | |
| * @returns {string} - Transform property supported by client. | |
| */ | |
| webkitOrNot = () => { | |
| const style = document.documentElement.style; | |
| if (typeof style.transform === 'string') { | |
| return 'transform'; | |
| } | |
| return 'WebkitTransform'; | |
| } | |
| /** | |
| * Builds the markup and attaches listeners to required events. | |
| */ | |
| init = () => { | |
| // Resize element on window resize | |
| window.addEventListener('resize', this.resizeHandler); | |
| // If element is draggable / swipable, add event handlers | |
| if (this.config.draggable) { | |
| // Touch events | |
| this.carouselContainer.addEventListener('touchstart', this.touchstartHandler, { passive: true }); | |
| this.carouselContainer.addEventListener('touchend', this.touchendHandler); | |
| this.carouselContainer.addEventListener('touchmove', this.touchmoveHandler, { passive: true }); | |
| // Mouse events | |
| this.carouselContainer.addEventListener('mousedown', this.mousedownHandler); | |
| this.carouselContainer.addEventListener('mouseup', this.mouseupHandler); | |
| this.carouselContainer.addEventListener('mouseleave', this.mouseleaveHandler); | |
| this.carouselContainer.addEventListener('mousemove', this.mousemoveHandler); | |
| } | |
| if (this.carouselContainer === null) { | |
| throw new Error('Something went wrong with the carousel ref binding 😭'); | |
| } | |
| // update perPage number dependable of user value | |
| this.resolveSlidesNumber(); | |
| // Apply styling and stuff | |
| this.refresh(); | |
| this.config.onInit.call(this); | |
| this.initialized = true; | |
| } | |
| /** | |
| * Determinates slides number accordingly to clients viewPort. | |
| */ | |
| resolveSlidesNumber = () => { | |
| if (typeof this.config.perPage === 'number') { | |
| this.perPage = this.config.perPage; | |
| } | |
| else if (typeof this.config.perPage === 'object') { | |
| this.perPage = 1; | |
| for (const viewPort in this.config.perPage) { | |
| if (this.viewPortWidth >= viewPort) { | |
| this.perPage = this.config.perPage[viewPort]; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Go to previous slide. | |
| * @param {number} [howManySlides=1] - How many items to slide backward. | |
| * @param {function} callback - Optional callback function. | |
| */ | |
| prev = (howManySlides = 1, callback) => { | |
| if (this.innerElements.length <= this.perPage) { | |
| return; | |
| } | |
| const beforeChange = this.currentSlide; | |
| if (this.currentSlide === 0 && this.config.loop) { | |
| this.currentSlide = this.innerElements.length - this.perPage; | |
| } | |
| else { | |
| this.currentSlide = Math.max(this.currentSlide - howManySlides, 0); | |
| } | |
| if (beforeChange !== this.currentSlide) { | |
| this.slideToCurrent(); | |
| this.config.onChange.call(this); | |
| if (callback) { | |
| callback.call(this); | |
| } | |
| } | |
| } | |
| /** | |
| * Go to next slide. | |
| * @param {number} [howManySlides=1] - How many items to slide forward. | |
| * @param {function} callback - Optional callback function. | |
| */ | |
| next = (howManySlides = 1, callback) => { | |
| if (this.innerElements.length <= this.perPage) { | |
| return; | |
| } | |
| const beforeChange = this.currentSlide; | |
| if (this.currentSlide === this.innerElements.length - this.perPage && this.config.loop) { | |
| this.currentSlide = 0; | |
| } | |
| else { | |
| this.currentSlide = Math.min(this.currentSlide + howManySlides, this.innerElements.length - this.perPage); | |
| } | |
| if (beforeChange !== this.currentSlide) { | |
| this.slideToCurrent(); | |
| this.config.onChange.call(this); | |
| if (callback) { | |
| callback.call(this); | |
| } | |
| } | |
| } | |
| /** | |
| * Go to slide with particular index | |
| * @param {number} index - Item index to slide to. | |
| * @param {function} callback - Optional callback function. | |
| */ | |
| goTo = (index, callback) => { | |
| if (this.innerElements.length <= this.perPage) { | |
| return; | |
| } | |
| const beforeChange = this.currentSlide; | |
| this.currentSlide = Math.min(Math.max(index, 0), this.innerElements.length - this.perPage); | |
| if (beforeChange !== this.currentSlide) { | |
| this.slideToCurrent(); | |
| this.config.onChange.call(this); | |
| if (callback) { | |
| callback.call(this); | |
| } | |
| } | |
| } | |
| /** | |
| * Moves sliders frame to position of currently active slide | |
| */ | |
| slideToCurrent = () => { | |
| this.sliderTransform = (this.currentSlide * (this.carouselWidth / this.perPage)) * -1; | |
| } | |
| /** | |
| * Recalculate drag /swipe event and reposition the frame of a slider | |
| */ | |
| updateAfterDrag = () => { | |
| const movement = this.drag.endX - this.drag.startX; | |
| const movementDistance = Math.abs(movement); | |
| const howManySliderToSlide = Math.ceil(movementDistance / (this.carouselWidth / this.perPage)); | |
| if (movement > 0 && movementDistance > this.config.threshold && this.innerElements.length > this.perPage) { | |
| this.prev(howManySliderToSlide); | |
| } | |
| else if (movement < 0 && movementDistance > this.config.threshold && this.innerElements.length > this.perPage) { | |
| this.next(howManySliderToSlide); | |
| } | |
| this.slideToCurrent(); | |
| } | |
| /** | |
| * When window resizes, resize slider components as well | |
| */ | |
| resizeHandler = () => { | |
| // Only resize width changes when a resize is not already running | |
| if(this.resizeAnimRunning || this.viewPortWidth === window.innerWidth) { | |
| return; | |
| } | |
| this.resizeAnimRunning = true; | |
| window.requestAnimationFrame(this.resizeWorker); | |
| } | |
| resizeWorker = () => { | |
| this.viewPortWidth = window.innerWidth; | |
| // update perPage number dependable of user value | |
| this.resolveSlidesNumber(); | |
| this.carouselWidth = this.carouselContainer.offsetWidth; | |
| this.sliderWidth = (this.carouselWidth / this.perPage) * this.innerElements.length; | |
| this.slideToCurrent(); | |
| this.resizeAnimRunning = false; | |
| } | |
| /** | |
| * Clear drag after touchend and mouseup event | |
| */ | |
| clearDrag = () => { | |
| this.drag = { | |
| startX: 0, | |
| endX: 0, | |
| startY: 0, | |
| letItGo: null | |
| }; | |
| } | |
| /** | |
| * touchstart event handler | |
| */ | |
| touchstartHandler = (e) => { | |
| e.stopPropagation(); | |
| this.pointerDown = true; | |
| this.drag.startX = e.touches[0].pageX; | |
| this.drag.startY = e.touches[0].pageY; | |
| } | |
| /** | |
| * touchend event handler | |
| */ | |
| touchendHandler = (e) => { | |
| e.stopPropagation(); | |
| this.pointerDown = false; | |
| if (this.drag.endX) { | |
| this.updateAfterDrag(); | |
| } | |
| this.clearDrag(); | |
| } | |
| /** | |
| * touchmove event handler | |
| */ | |
| touchmoveHandler = (e) => { | |
| e.stopPropagation(); | |
| if (this.drag.letItGo === null) { | |
| this.drag.letItGo = Math.abs(this.drag.startY - e.touches[0].pageY) < Math.abs(this.drag.startX - e.touches[0].pageX); | |
| } | |
| if (this.pointerDown && this.drag.letItGo) { | |
| if(this.transformDuration !== 0) { | |
| this.transformDuration = 0; | |
| } | |
| this.drag.endX = e.touches[0].pageX; | |
| if (!this.slideAnimRunning) { | |
| this.slideAnimRunning = true; | |
| requestAnimationFrame(() => this.touchmoveWorker(e)); | |
| } | |
| } | |
| } | |
| touchmoveWorker = (e) => { | |
| this.sliderTransform = (this.currentSlide * (this.carouselWidth / this.perPage) + (this.drag.startX - this.drag.endX)) * -1; | |
| this.slideAnimRunning = false; | |
| } | |
| /** | |
| * mousedown event handler | |
| */ | |
| mousedownHandler = (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| this.pointerDown = true; | |
| this.drag.startX = e.pageX; | |
| } | |
| /** | |
| * mouseup event handler | |
| */ | |
| mouseupHandler =(e) => { | |
| e.stopPropagation(); | |
| this.pointerDown = false; | |
| this.transformDuration = this.config.duration; | |
| if (this.drag.endX) { | |
| this.updateAfterDrag(); | |
| } | |
| this.clearDrag(); | |
| } | |
| /** | |
| * mousemove event handler | |
| */ | |
| mousemoveHandler = (e) => { | |
| e.preventDefault(); | |
| if (this.pointerDown) { | |
| this.drag.endX = e.pageX; | |
| if (!this.slideAnimRunning) { | |
| if(this.transformDuration !== 0) { | |
| this.transformDuration = 0; | |
| } | |
| this.slideAnimRunning = true; | |
| requestAnimationFrame(this.mousemoveWorker); | |
| } | |
| } | |
| } | |
| mousemoveWorker = () => { | |
| this.sliderTransform = (this.currentSlide * (this.carouselWidth / this.perPage) + (this.drag.startX - this.drag.endX)) * -1; | |
| this.slideAnimRunning = false; | |
| } | |
| /** | |
| * mouseleave event handler | |
| */ | |
| mouseleaveHandler = (e) => { | |
| if (this.pointerDown) { | |
| this.pointerDown = false; | |
| this.drag.endX = e.pageX; | |
| this.transformDuration = this.config.duration; | |
| this.updateAfterDrag(); | |
| this.clearDrag(); | |
| } | |
| } | |
| /** | |
| * Update after removing, prepending or appending items. | |
| */ | |
| refresh = () => { | |
| this.sliderWidth = (this.carouselWidth / this.perPage) * this.innerElements.length; | |
| // Loop through the slides and add styling | |
| Array.prototype.forEach.call(this.innerElements, element => { | |
| element.style.width = `${100 / this.innerElements.length}%`; | |
| }); | |
| // Go to currently active slide after initial build | |
| this.slideToCurrent(); | |
| } | |
| preventClickOnDrag = (event) => {} | |
| } |
This file contains hidden or 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
| <!doctype html> | |
| <html> | |
| <head> | |
| <title>Aurelia</title> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous"> | |
| </head> | |
| <body aurelia-app> | |
| <h1>Loading...</h1> | |
| <script src="https://jdanyow.github.io/rjs-bundle/node_modules/requirejs/require.js"></script> | |
| <script src="https://jdanyow.github.io/rjs-bundle/config.js"></script> | |
| <script src="https://jdanyow.github.io/rjs-bundle/bundles/aurelia.js"></script> | |
| <script src="https://jdanyow.github.io/rjs-bundle/bundles/babel.js"></script> | |
| <script> | |
| require(['aurelia-bootstrapper']); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment