Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add looping #33

Merged
merged 23 commits into from
Feb 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a3bacc6
Stubbing out looping prop and docs
weotch Feb 19, 2022
94e0c11
Initial implementation of cloneVnode
weotch Feb 20, 2022
d3de0fe
CS
weotch Feb 20, 2022
1f7a21a
Testing nesting a component in a cloned vnode
weotch Feb 20, 2022
3e078bb
Switching back to simple slides for testing
weotch Feb 20, 2022
6981b7b
Renaming prop to “loop”
weotch Feb 20, 2022
5c92b46
Filter out the text vnode slides earlier
weotch Feb 20, 2022
a7af62f
Implement looping arrow behavior
weotch Feb 20, 2022
739ce15
Move arrows out of mask
weotch Feb 20, 2022
8fdd103
Comment typos
weotch Feb 20, 2022
c28dae9
Disable test cloneVnode code
weotch Feb 20, 2022
e8af12f
Support index outside of the normal range when looping
weotch Feb 20, 2022
a0e5c7d
Making dot clicks work with looping
weotch Feb 20, 2022
fcb025d
Fix drag snapping when dragging left
weotch Feb 20, 2022
67cb449
Moving slottedSlides into the more generic pagination concern
weotch Feb 21, 2022
dcee778
More stable boundedIndex calculation
weotch Feb 21, 2022
a747f36
Reorder slides and add track offset to accomplish looping effect
weotch Feb 21, 2022
7be4741
Updating vue-visual to prevent flicker when looping
weotch Feb 21, 2022
89e31c9
Improve formatting of p tags in demo site
weotch Feb 21, 2022
317eaba
Implement filling incomplete pages
weotch Feb 21, 2022
2124c19
Changing looping title
weotch Feb 21, 2022
cbb77d2
Remove cloning elements as a concern of looping
weotch Feb 21, 2022
9ac74d3
Adding AKA note to looping docs
weotch Feb 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions demo/components/layout/nav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
]

Expand Down
4 changes: 3 additions & 1 deletion demo/components/slide.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<template lang='pug'>

.slide: div
.title Slide {{ index }}
.title(v-if='index') Slide {{ index }}
slot

</template>
Expand Down Expand Up @@ -36,6 +36,8 @@ export default
flex-center()
text-align center
fluid-space padding 's'
> div
width 100%

// Increase slide text size
.title
Expand Down
79 changes: 79 additions & 0 deletions demo/content/looping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
title: 'Looping'
---

## Basic looping

Looping is also known as `wrapAround` or `infinite` in other carousels.

<ssr-carousel :slides-per-page='1' loop show-dots show-arrows>
<slide :index='1'></slide>
<slide :index='2'></slide>
<slide :index='3'></slide>
</ssr-carousel>

```vue
<ssr-carousel :slides-per-page='1' loop show-dots show-arrows>
<slide :index='1'></slide>
<slide :index='2'></slide>
<slide :index='3'></slide>
</ssr-carousel>
```

## 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.

<ssr-carousel :slides-per-page='2' loop show-dots show-arrows>
<slide :index='1'></slide>
<slide :index='2'></slide>
<slide :index='3'></slide>
</ssr-carousel>

```vue
<ssr-carousel :slides-per-page='2' loop show-dots show-arrows>
<slide :index='1'></slide>
<slide :index='2'></slide>
<slide :index='3'></slide>
</ssr-carousel>
```

## 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.

<ssr-carousel :slides-per-page='1' loop>
<slide>
<visual
image='https://via.placeholder.com/1920x1080?text=Slide+1'
lazyload
:aspect='16/9'>
</visual>
</slide>
<slide>
<visual
image='https://via.placeholder.com/1920x1080?text=Slide+2'
lazyload
:aspect='16/9'>
</visual>
</slide>
</ssr-carousel>

```vue
<ssr-carousel :slides-per-page='1' loop>
<slide>
<visual
image='https://via.placeholder.com/1920x1080?text=Slide+1'
lazyload
:aspect='16/9'>
</visual>
</slide>
<slide>
<visual
image='https://via.placeholder.com/1920x1080?text=Slide+2'
lazyload
:aspect='16/9'>
</visual>
</slide>
</ssr-carousel>
```
3 changes: 2 additions & 1 deletion demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
22 changes: 18 additions & 4 deletions demo/pages/_page.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ article
<script lang='coffee'>
import pageMixin from '@bkwld/cloak/mixins/page'
import SsrCarousel from '../../src/ssr-carousel'
import Visual from 'vue-visual'
import 'vue-visual/index.css'

export default
name: 'Page'
mixins: [ pageMixin ]

components: { SsrCarousel }
components: { SsrCarousel, Visual }

# Get Tower data
asyncData: ({ app, params, $content }) ->
Expand Down Expand Up @@ -78,18 +80,30 @@ h1

// Seperate regions on a page
h2
fluid-space margin-top, 'm'
fluid-space margin-bottom, 's'
fluid-space margin-top, 'l'
style-h2()

// Syntax highlighting
>>> pre
background darken(primary-background, 15%)
border 1px solid darken(primary-background, 30%)
basic-border-radius()
fluid-space margin-v, 's'
fluid-space margin-bottom, 's'
>>> code
font-size 14px
line-height 1.2

// Style body text, like notes
p
line-height 1.5
color lighten(primary-background, 50%)

// Underline links
a
text-decoration underline

// Add constant margins around demos
.ssr-carousel
fluid-space margin-v, 's'

</style>
8 changes: 4 additions & 4 deletions demo/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10574,10 +10574,10 @@ vue-unorphan@^1.2.3:
dependencies:
unorphan "^1.2.1"

vue-visual@^2.0.0:
version "2.5.4"
resolved "https://registry.yarnpkg.com/vue-visual/-/vue-visual-2.5.4.tgz#3675bdea3a748bfc56775d88cdeaf27bd0f5e1c2"
integrity sha512-UUMxj0l9vPyR9W28y3g5eel4JsznuBgaqgEm8EhG/Vi2QVhhaDRsTUj0fkN++YcmwXt2iJcpewRDy6ITxQsHjQ==
vue-visual@^2.0.0, vue-visual@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/vue-visual/-/vue-visual-2.6.0.tgz#083c6f83d755d59819db63e10ad249deef4da358"
integrity sha512-clCxWyA8YwjFOUSR+9vXXUomBto4v/+PqP/0DXtsIvOAACnKyDXtKRvHZzTY0EOfNinWZ/7NzTZ0aXgeYhJpVg==

vue@^2.6.12:
version "2.6.14"
Expand Down
38 changes: 23 additions & 15 deletions src/concerns/dragging.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,28 @@ export default

computed:

# The current slide or page index. It rounds differently depedning on the
# The current slide or page index. It rounds differently depending on the
# direction of the velocity. So that it eases to a stop in the direction
# the user was dragging
dragIndex: ->
fractionalIndex = Math.abs if @paginateBySlide
then @currentX / @slideWidth
else @currentX / @pageWidth
switch
# the user was dragging.
dragIndex: -> switch

# If there is very little velocity, go to the closet page
when Math.abs(@dragVelocity) <= 2 then Math.round fractionalIndex
# If there is very little velocity, go to the closet page
when Math.abs(@dragVelocity) <= 2 then Math.round @fractionalIndex

# User was moving forward
when @dragVelocity < 0 then Math.ceil fractionalIndex
# User was moving forward
when @dragVelocity < 0 then Math.ceil @fractionalIndex

# User was moving backward
else Math.floor fractionalIndex
# User was moving backward
else Math.floor @fractionalIndex

# Determine the current index given the currentX as a fraction. For
# instance, when dragging forward, it will be like 0.1 and when you've
# dragged almost a full page, forward it would be 0.9.
fractionalIndex: ->
x = @currentX - @currentIncompletePageOffset
if @paginateBySlide
then x / @slideWidth * -1
else x / @pageWidth * -1

# Calculate the width of a slide
slideWidth: -> @pageWidth / @currentSlidesPerPage
Expand Down Expand Up @@ -98,7 +103,7 @@ export default
else

# Tween so the track is in bounds if it was out
if @isOutOfBounds
if @isOutOfBounds and not @loop
@targetX = @applyXBoundaries @currentX
@startTweening()

Expand Down Expand Up @@ -171,12 +176,15 @@ export default

# Prevent dragging from exceeding the min/max edges
applyBoundaryDampening: (x) -> switch
when @loop then x # Don't apply dampening
when x > 0 then Math.pow x, @boundaryDampening
when x < @endX then @endX - Math.pow @endX - x, @boundaryDampening
else @applyXBoundaries x

# Constraint the x value to the min and max values
applyXBoundaries: (x) -> Math.max @endX, Math.min 0, x
applyXBoundaries: (x) ->
if @loop then x # Don't apply boundaries
else Math.max @endX, Math.min 0, x

# Prevent the anchors and images from being draggable (like via their
# ghost outlines). Using this approach because the draggable html attribute
Expand Down
33 changes: 33 additions & 0 deletions src/concerns/looping.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
###
Code related to looping / infinite scroll
###
export default

# Add prop to enable looping
props: loop: Boolean

computed:

# Put slides in order, applying rules related to looping
slides: ->

# If not looping, don't show other slides during boundary dampening
return @slottedSlides unless @loop

# Breakup the slides into arrays using the modulo of the current
# side index. I came up with this by figuring out how the arrays should
# look and working back from there.
prepended = @slottedSlides.slice @currentSlideIndex % @slidesCount
remainder = @slottedSlides.slice 0, @slidesCount - prepended.length
return [ ...prepended, ...remainder ]

# This represents the current (as in while scrolling / animating) left most
# slide index. This is used in looping calculation so that the reordering
# of slides isn't affected by paginatePerSlide setting.
currentSlideIndex: -> Math.floor @currentX / @slideWidth * -1

# When looping, slides get re-ordered. This value is added to the
# track transform so that the slides don't feel like they were re-ordered.
trackOffset: ->
unless @loop then 0
else @currentSlideIndex * @slideWidth
52 changes: 40 additions & 12 deletions src/concerns/pagination.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default
paginateBySlide: Boolean

data: ->
index: 0 # The current page
index: 0 # The current page; when looping may exceed slideCount
currentX: 0 # The actual left offset of the slides container
targetX: 0 # Where we may be tweening the slide to

Expand All @@ -24,37 +24,65 @@ export default
# Disable carousel-ness when there aren't enough slides
disabled: -> @slidesCount <= @currentSlidesPerPage

# Filter out slides that have a "text" property, these aren't actual
# elements. They are whitespace, like newlines.
slidesCount: ->
(@$slots.default || [])
.filter (vnode) -> !vnode?.text
.length
# Get just the slotted slides that are components, ignoring text nodes
# which may exist as a result of whitespace
slottedSlides: -> @$slots.default.filter (vnode) -> !!vnode.tag

# Get the total number of slides
slidesCount: -> @slottedSlides.length

# Apply boundaries to the index, which will exceed them when looping
boundedIndex: -> Math.abs(@index) % @pages

# The current incomplete page offset
currentIncompletePageOffset: -> @makeIncompletePageOffset @index

watch:

# Emit events on index change
index: -> @$emit 'change', { @index }
boundedIndex: -> @$emit 'change', { @boundedIndex }

methods:

# Advance methods
next: -> @goto @index + 1
back: -> @goto @index - 1

# The dots are ignorant of looping, so convert their bounded index to the
# true index so we don't animate through a ton of pages going to the
# clicked dot.
gotoDot: (dotIndex) -> @goto dotIndex - @boundedIndex + @index

# Go to a specific index
goto: (index) ->
@index = @applyIndexBoundaries index
@tweenToIndex @index

# Tween to a specific index
tweenToIndex: (index) ->

# Figure out the new x position
x = if @paginateBySlide
then index * @slideWidth
else index * @pageWidth
@targetX = @applyXBoundaries -1 * x
then index * @slideWidth * -1
else index * @pageWidth * -1

# Apply adjustments to x value and persist
x += @makeIncompletePageOffset index
@targetX = @applyXBoundaries x

# Start tweening
@startTweening()

# Creates a px value to represent adjustments that should be made to
# account for incommplete pages of slides when looping is enbaled. Like
# when there is 3 slotted slides and 2 slides per page.
makeIncompletePageOffset: (index) ->
return 0 unless @loop and not @paginateBySlide
Math.floor(index / @pages) *
(@slidesCount % @currentSlidesPerPage) *
@slideWidth

# Apply boundaries to the index
applyIndexBoundaries: (index) ->
Math.max 0, Math.min @pages - 1, index
if @loop then index
else Math.max 0, Math.min @pages - 1, index
5 changes: 3 additions & 2 deletions src/ssr-carousel-arrows.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ export default
props:
index: Number
pages: Number
loop: Boolean

computed:

# Determine if button should be disabled because we're at the limits
backDisabled: -> @index == 0
nextDisabled: -> @index == @pages - 1
backDisabled: -> @index == 0 unless @loop
nextDisabled: -> @index == @pages - 1 unless @loop

</script>

Expand Down
Loading