Autoplay

In Vue Horizontal, you can programmatically navigate horizontal content with the exposed methods. Although there isn't a built-in capability for autoplay, Vue Horizontal is created to support autoplay by exposing useful methods, events and props to accomplish that. You could use mounted to trigger the autoplay, but a more reliable method would be to trigger them as it enter into the viewport. This can be achieved with IntersectionObserver but for simplicity, you can use vue-observe-visibility as well.

Vue Horizontal methods

  • $refs.horizontal.next()
  • $refs.horizontal.prev()
  • $refs.horizontal.scrollToIndex(1)
  • $refs.horizontal.scrollToLeft(1000)

Vue Horizontal events

  • <vue-horizontal @scroll="({...}) => ">
  • <vue-horizontal @scroll-debounce="({...}) => ">

Vue Horizontal props

  • :button="false" to hide nav button.
  • :displacement="0.5" to control displacement of prev/next clicks relative to the width of the content.
  • snap="start|center|end|none" to change the snapping settings

Autoplay

Mounted

You can programmatically move to any position of the horizontal content in Vue Horizontal. This allows you to easily move to preset initial position at mounted.

0

1

2

3

4

5

6

7

8

9

Mounted.vueimport=others/autoplay/recipes-autoplay-initial.vue
<template>
  <vue-horizontal responsive ref="horizontal">
    <placeholder-component v-for="i in [0,1,2,3,4,5,6,7,8,9]" :key="i">
      {{i}}
    </placeholder-component>
  </vue-horizontal>
</template>

<script>
export default {
  mounted() {
    this.$refs.horizontal.scrollToIndex(1);
  }
}
</script>

Peeking

When Vue Horizontal is visible, move the content slightly to the right for a few seconds to indicate there are more content on the right. For simplicity, you can use vue-observe-visibility.

0

1

2

3

4

5

6

7

8

9

Peeking.vueimport=others/autoplay/recipes-autoplay-peeking.vue
<template>
  <vue-horizontal responsive ref="horizontal" :snap="snap" :button="peeked">
    <placeholder-component v-for="i in [0,1,2,3,4,5,6,7,8,9]" :key="i">
      {{ i }}
    </placeholder-component>
  </vue-horizontal>
</template>

<script>
export default {
  data() {
    return {
      peeked: false,
      timeout: null,
    }
  },
  computed: {
    snap() {
      return this.peeked ? 'snap' : 'none'
    }
  },
  mounted() {
    // Custom observe visibility is below
    // Much easier way: https://www.npmjs.com/package/vue-observe-visibility
    observeVisibility(this.$refs.horizontal.$el, this.peeking)
  },
  destroyed() {
    if (this.timeout) {
      clearTimeout(this.timeout)
    }
  },
  methods: {
    peeking(visible) {
      if (!visible) {
        return
      }

      this.peeked = false

      this.timeout = setTimeout(() => {
        this.$refs.horizontal.scrollToLeft(48)

        this.timeout = setTimeout(() => {
          this.$refs.horizontal.scrollToLeft(0)

          this.timeout = setTimeout(() => {
            this.peeked = true
          }, 1000)
        }, 400)
      }, 1000)
    },

  }
}

/**
 * Custom function, much easier way: https://www.npmjs.com/package/vue-observe-visibility
 *
 * @param element to track visibility
 * @param callback: function(boolean) when visibility change
 */
function observeVisibility(element, callback) {
  const observer = new IntersectionObserver((records) => {
    callback(records.find(record => record.isIntersecting))
  }, {rootMargin: '10% 0% 10% 0%', threshold: 1.0});
  observer.observe(element);
}
</script>

Sequences

Forward and reverse

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

FrowardReverse.vueimport=others/autoplay/recipes-autoplay-forward-reverse.vue
<template>
  <vue-horizontal responsive ref="horizontal" @scroll-debounce="onScrollDebounce">
    <placeholder-component v-for="i in items" :key="i">
      {{ i }}
    </placeholder-component>
  </vue-horizontal>
</template>

<script>
export default {
  data() {
    return {
      items: [...Array(30).keys()],
      hasPrev: false,
      hasNext: false,
      interval: null,
      forward: true,
    }
  },
  mounted() {
    // Custom observe visibility is below
    // Much easier way: https://www.npmjs.com/package/vue-observe-visibility
    observeVisibility(this.$refs.horizontal.$el, (visible) => {
      if (visible) {
        this.interval = setInterval(this.play, 3000)
      } else {
        clearInterval(this.interval)
      }
    })
  },
  destroyed() {
    clearInterval(this.interval)
  },
  methods: {
    onScrollDebounce({hasNext, hasPrev}) {
      this.hasPrev = hasPrev
      this.hasNext = hasNext
    },
    play() {
      // Check if direction need to be reversed
      if (this.hasNext !== this.hasPrev) {
        this.forward = !this.forward
      }

      if (this.forward && this.hasNext) {
        this.$refs.horizontal.next()
      } else if (!this.forward && this.hasPrev) {
        this.$refs.horizontal.prev()
      }
    }
  }
}

/**
 * Custom function, much easier way: https://www.npmjs.com/package/vue-observe-visibility
 *
 * @param element to track visibility
 * @param callback: function(boolean) when visibility change
 */
function observeVisibility(element, callback) {
  const observer = new IntersectionObserver((records) => {
    callback(records.find(record => record.isIntersecting))
  }, {rootMargin: '10% 0% 10% 0%', threshold: 1.0});
  observer.observe(element);
}
</script>

Forward and reset

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

ForwardReset.vueimport=others/autoplay/recipes-autoplay-forward-reset.vue
<template>
  <vue-horizontal responsive ref="horizontal" @scroll-debounce="onScrollDebounce" :displacement="displacement">
    <placeholder-component v-for="i in items" :key="i">
      {{ i }}
    </placeholder-component>
  </vue-horizontal>
</template>

<script>
export default {
  data() {
    return {
      items: [...Array(30).keys()],
      hasPrev: false,
      hasNext: false,
      interval: null,
      // relative width to move when next/prev is clicked.
      displacement: 1.0,
    }
  },
  mounted() {
    // Custom observe visibility is below
    // Much easier way: https://www.npmjs.com/package/vue-observe-visibility
    observeVisibility(this.$refs.horizontal.$el, (visible) => {
      if (visible) {
        this.interval = setInterval(this.play, 3000)
      } else {
        clearInterval(this.interval)
      }
    })
  },
  destroyed() {
    clearInterval(this.interval)
  },
  methods: {
    onScrollDebounce({hasNext, hasPrev}) {
      this.hasPrev = hasPrev
      this.hasNext = hasNext
    },
    play() {
      if (!this.hasNext && this.hasPrev) {
        this.$refs.horizontal.scrollToIndex(0)
        this.displacement = 1.0
        return
      }

      if (this.hasNext) {
        this.$refs.horizontal.next()

        // After first nav, change displacement window to just 60%
        this.displacement = 0.6
      }
    }
  }
}

/**
 * Custom function, much easier way: https://www.npmjs.com/package/vue-observe-visibility
 *
 * @param element to track visibility
 * @param callback: function(boolean) when visibility change
 */
function observeVisibility(element, callback) {
  const observer = new IntersectionObserver((records) => {
    callback(records.find(record => record.isIntersecting))
  }, {rootMargin: '10% 0% 10% 0%', threshold: 1.0});
  observer.observe(element);
}
</script>

Programmatically

Tracking

Track another scrollbar or vue horizontal and move it to the same position. In this example, B tracking the position of A.

Header A

a0

a1

a2

a3

a4

a5

a6

a7

a8

a9

a10

a11

a12

a13

a14

a15

a16

a17

a18

a19

a20

a21

a22

a23

a24

a25

a26

a27

a28

a29

Header B

b0

b1

b2

b3

b4

b5

b6

b7

b8

b9

b10

b11

b12

b13

b14

b15

b16

b17

b18

b19

b20

b21

b22

b23

b24

b25

b26

b27

b28

b29

Tracking.vueimport=others/autoplay/recipes-autoplay-tracking.vue
<template>
  <div>
    <h3>Header A</h3>

    <vue-horizontal responsive @scroll="onScroll">
      <placeholder-component v-for="i in items" :key="i">
        a{{ i }}
      </placeholder-component>
    </vue-horizontal>

    <h3 style="margin-top: 24px">Header B</h3>

    <vue-horizontal responsive ref="b" snap="none" class="horizontal">
      <placeholder-component v-for="i in items" :key="i">
        b{{ i }}
      </placeholder-component>
    </vue-horizontal>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [...Array(30).keys()],
    }
  },
  methods: {
    onScroll({left}) {
      this.$refs.b.scrollToLeft(left, "auto")
    },
  }
}
</script>

<style scoped>
.horizontal >>> .v-hl-container {
  scroll-behavior: initial;
}
</style>

Mirror

Mirror another vue horizontal and move it to the same index. A and B mirroring each other position, updated after scroll debounce.

Header A

a0

a1

a2

a3

a4

a5

a6

a7

a8

a9

a10

a11

a12

a13

a14

a15

a16

a17

a18

a19

a20

a21

a22

a23

a24

a25

a26

a27

a28

a29

Header B

b0

b1

b2

b3

b4

b5

b6

b7

b8

b9

b10

b11

b12

b13

b14

b15

b16

b17

b18

b19

b20

b21

b22

b23

b24

b25

b26

b27

b28

b29

Mirror.vueimport=others/autoplay/recipes-autoplay-mirror.vue
<template>
  <div>
    <h3>Header A</h3>

    <vue-horizontal responsive ref="a" @scroll-debounce="onScrollDebounce">
      <placeholder-component v-for="i in items" :key="i">
        a{{ i }}
      </placeholder-component>
    </vue-horizontal>

    <h3 style="margin-top: 24px">Header B</h3>

    <vue-horizontal responsive ref="b" @scroll-debounce="onScrollDebounce">
      <placeholder-component v-for="i in items" :key="i">
        b{{ i }}
      </placeholder-component>
    </vue-horizontal>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [...Array(30).keys()],
    }
  },
  methods: {
    onScrollDebounce({left}) {
      this.$refs.a.scrollToLeft(left)
      this.$refs.b.scrollToLeft(left)
    },
  }
}
</script>