diff --git a/README.md b/README.md index b6b4903..de7ac88 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ For more examples, see the demo: https://vue-ssr-carousel.netlify.app. - `slides-per-page` (`1`) - How many slides are shown per page. - `gutter` (`20`) - The size of the space between slides. This can a number or any CSS resolvable string. See https://vue-ssr-carousel.netlify.app/gutters. - `responsive` (`[]`) - Adjust settings at breakpoints. See https://vue-ssr-carousel.netlify.app/responsive. +- `loop` (`false`) - Boolean to enable looping / infinite scroll. - `paginate-by-slide` (`false`) - When `false`, dragging the carousel or interacting with the arrows will advance a full page of slides at a time. When `true`, the carousel will come to a rest at each slide. - `show-arrows` (`false`) - Whether to show back/forward arrows. See https://vue-ssr-carousel.netlify.app/ui. - `show-dots` (`false`) - Whether to show dot style pagination dots. See https://vue-ssr-carousel.netlify.app/ui. diff --git a/demo/components/layout/nav.vue b/demo/components/layout/nav.vue index 2815e56..a769086 100644 --- a/demo/components/layout/nav.vue +++ b/demo/components/layout/nav.vue @@ -19,6 +19,7 @@ export default { title: 'Responsive', path: '/responsive' } { title: 'Gutters', path: '/gutters' } { title: 'UI', path: '/ui' } + { title: 'Looping', path: '/looping' } { title: 'Miscellaneous', path: '/misc' } ] diff --git a/demo/components/slide.vue b/demo/components/slide.vue index 20bc501..eda81b8 100644 --- a/demo/components/slide.vue +++ b/demo/components/slide.vue @@ -3,7 +3,7 @@ @@ -36,6 +36,8 @@ export default flex-center() text-align center fluid-space padding 's' + > div + width 100% // Increase slide text size .title diff --git a/demo/content/looping.md b/demo/content/looping.md new file mode 100644 index 0000000..dda2a16 --- /dev/null +++ b/demo/content/looping.md @@ -0,0 +1,79 @@ +--- +title: 'Looping' +--- + +## Basic looping + +Looping is also known as `wrapAround` or `infinite` in other carousels. + + + + + + + +```vue + + + + + +``` + +## Looping with multiple slides per page + +Note how the incomplete 2nd page is handled. The 3rd and 1st slide are shown simulataneously. On the next advance forward, the track advances a half width so that the *new* first page contains the 1st and 2nd slide. + + + + + + + +```vue + + + + + +``` + +## Cloned slides can contain components + +In this case, we're using [vue-visual](https://github.com/BKWLD/vue-visual) components to render image assets. Note how lazy loading prevents the loading of the second image until you advance forward. + + + + + + + + + + + + +```vue + + + + + + + + + + +``` diff --git a/demo/package.json b/demo/package.json index 34ab1bf..6202400 100644 --- a/demo/package.json +++ b/demo/package.json @@ -21,6 +21,7 @@ "prism-themes": "^1.9.0", "vue-balance-text": "^1.2.3", "vue-detachable-header": "^0.2.0", - "vue-unorphan": "^1.2.3" + "vue-unorphan": "^1.2.3", + "vue-visual": "^2.6.0" } } diff --git a/demo/pages/_page.vue b/demo/pages/_page.vue index 95f1a78..2c3a3bd 100644 --- a/demo/pages/_page.vue +++ b/demo/pages/_page.vue @@ -21,12 +21,14 @@ article diff --git a/src/ssr-carousel-dots.vue b/src/ssr-carousel-dots.vue index 041af16..3c54127 100644 --- a/src/ssr-carousel-dots.vue +++ b/src/ssr-carousel-dots.vue @@ -6,7 +6,7 @@ button.ssr-carousel-dot-button( v-for='i in pages' :key='i' :aria-label='`Page ${i}`' - :disabled='index == i - 1' + :disabled='boundedIndex == i - 1' @click='$emit("goto", i - 1)') slot(v-if='$slots.dot' name='dot') span.ssr-carousel-dot-icon(v-else) @@ -19,7 +19,7 @@ export default props: - index: Number + boundedIndex: Number pages: Number diff --git a/src/ssr-carousel-track.vue b/src/ssr-carousel-track.vue index 6370c08..0b35b0f 100644 --- a/src/ssr-carousel-track.vue +++ b/src/ssr-carousel-track.vue @@ -9,19 +9,20 @@ export default props: dragging: Boolean currentX: Number + trackOffset: Number + slides: Array computed: # Styles that are used to position the track - styles: -> transform: "translateX(#{@currentX}px)" + styles: -> transform: "translateX(#{@currentX + @trackOffset}px)" # Render the track and slotted slides render: (create) -> # Wrap the slides in ssr-carousel-slide functional components - children = @$slots.default.map (child) -> - if child.text then child # Text nodes like newlines - else create SsrCarouselSlide, { parent: this }, [ child ] + children = @slides.map (child) -> + create SsrCarouselSlide, { parent: this }, [ child ] # Create the track div create 'div', diff --git a/src/ssr-carousel.vue b/src/ssr-carousel.vue index 8ccddd5..c22e54c 100644 --- a/src/ssr-carousel.vue +++ b/src/ssr-carousel.vue @@ -7,23 +7,27 @@ //- Render generated styles component(is='style' v-html='instanceStyles') - //- The overflow mask and drag target - .ssr-carousel-mask( - :class='{ pressing, disabled }' - v-on='maskListeners') - - //- The container of the slides that animates - ssr-carousel-track( - ref='track' - v-bind='{ dragging, currentX }') - - //- Slides are injected here - slot + //- Container so that arrows can be centered relative to slides but not be + //- within the mask's overflow:hidden (which prevents overriding styles from + //- positioning outside of carousel). + .ssr-carousel-slides + + //- The overflow mask and drag target + .ssr-carousel-mask( + :class='{ pressing, disabled }' + v-on='maskListeners') + + //- The container of the slides that animates + ssr-carousel-track( + ref='track' + :slides='slides' + v-bind='{ dragging, currentX, trackOffset }') //- Back / Next navigation ssr-carousel-arrows( v-if='showArrows' v-bind='{ index, pages }' + :loop='loop' @back='back' @next='next') template(#back): slot(name='back-arrow') @@ -32,8 +36,8 @@ //- Dots navigation ssr-carousel-dots( v-if='showDots' - v-bind='{ index, pages }' - @goto='goto') + v-bind='{ boundedIndex, pages }' + @goto='gotoDot') template(#dot): slot(name='dot') @@ -51,6 +55,7 @@ import SsrCarouselTrack from './ssr-carousel-track' import autoplay from './concerns/autoplay' import dragging from './concerns/dragging' import focus from './concerns/focus' +import looping from './concerns/looping' import pagination from './concerns/pagination' import responsive from './concerns/responsive' import tweening from './concerns/tweening' @@ -64,6 +69,7 @@ export default autoplay dragging focus + looping pagination responsive tweening @@ -82,8 +88,11 @@ export default showDots: Boolean computed: + + # Determine whether to create hover event bindings watchesHover: -> @autoplayDelay > 0 + # Create event bindings maskListeners: -> return {} if @disabled { @@ -105,6 +114,10 @@ export default .ssr-carousel touch-action pan-y +// Posiition arrows relative to this +.ssr-carousel-slides + position relative + // Mask around slides .ssr-carousel-mask overflow hidden