Features
Using Vue Horizontal
You can display horizontal content like you would do with just any other HTML content vertically.
Naturally, that would allow you to use v-for
for a consistent design template.
- Any HTML structure works, you can mix and match them up.
- Use
v-for
and/orv-if
with anydiv
orcomponent
- Navigation left, right button will automatically appear if there are any items overflowing
- Trackpad and touch scrolling will work as expected.
<template>
<vue-horizontal>
<div class="item">
<h3>HTML Tag</h3>
<p>As you can see these are just html elements.</p>
</div>
<section>
<h4>Don't have to be the same tag</h4>
<p>I used a h4 instead of a h3</p>
</section>
<section>
<h3>Navigation Button</h3>
<p>The navigation button will appear if there is an overflow.</p>
</section>
<section>
<h3>Scroll</h3>
<p>You can just trackpad to scroll still!</p>
</section>
<section>
<h3>Touch screen</h3>
<p>Touch screen works too!</p>
</section>
<section v-for="item in items" :key="item.i">
<h3>{{ item.title }}</h3>
<p>{{ item.content }}</p>
</section>
<section>
<h3>Last item?</h3>
<p>Maybe you want to display something different at the end?</p>
</section>
</vue-horizontal>
</template>
<script>
export default {
data() {
return {
// E.g: creates 5 array items...
items: [...Array(5).keys()].map((i) => {
return {i, title: `v-for: ${i}`, content: `🚀 Paragraph ${i}`};
}),
}
}
}
</script>
<style scoped>
section,
.item {
background: #f3f3f3;
padding: 16px 24px;
margin-right: 24px;
}
</style>
Responsive
It comes with default set of responsive breakpoints that is disabled by default.
We believe that CSS should be written by the users and not controlled by a library.
However, for the sake of convenience, you can use enable it with the responsive
prop.
Check out responsive 101 for a detailed write-up about responsive horizontal design.
Breakpoints
They are created in CSS with media queries, the viewport of the screen (your browser actual width).
< 640px
1 item in container< 768px
2 item in container< 1024px
3 item in container< 1280px
4 item in container> 1280px
5 item in container
<template>
<vue-horizontal responsive>
<section v-for="item in items" :key="item.i">
<h4>{{ item.title }}</h4>
<p>{{ item.content }}</p>
</section>
</vue-horizontal>
</template>
<script>
export default {
data() {
return {
// E.g: creates 20 array items...
items: [...Array(20).keys()].map((i) => {
return {i, title: `Responsive`, content: `Content`};
}),
}
}
}
</script>
<style scoped>
section {
padding: 16px 24px;
background: #f3f3f3;
}
</style>
Scroll snapping
<vue-horizontal snap="start|center|end">
- start (default)
- center
- end
- none to turn it off
<vue-horizontal snap="start">
<template>
<vue-horizontal snap="start">
<component-example v-for="i in [1,2,3,4,5,6,7,8]" :key="i">
<h3>Start {{i}}</h3>
</component-example>
</vue-horizontal>
</template>
<vue-horizontal snap="center">
<template>
<vue-horizontal snap="center">
<component-example v-for="i in [1,2,3,4,5,6,7,8]" :key="i">
<h3>Center {{i}}</h3>
</component-example>
</vue-horizontal>
</template>
<vue-horizontal snap="end">
<template>
<vue-horizontal snap="end">
<component-example v-for="i in [1,2,3,4,5,6,7,8]" :key="i">
<h3>End {{i}}</h3>
</component-example>
</vue-horizontal>
</template>
<vue-horizontal snap="none">
<template>
<vue-horizontal snap="none">
<component-example v-for="i in [1,2,3,4,5,6,7,8]" :key="i">
<h3>Center {{i}}</h3>
</component-example>
</vue-horizontal>
</template>
Scroll bar
The Scroll bar is disabled by default, you can enable it the scroll
prop.
<vue-horizontal scroll>
<template>
<vue-horizontal scroll>
<component-example class="component" v-for="i in [1,2,3,4,5,6,7,8]" :key="i">
<h3>Scroll bar {{i}}</h3>
</component-example>
</vue-horizontal>
</template>
<style scoped>
.component {
margin-bottom: 24px;
}
</style>
Navigation button
- Due to the varying nature of margins, padding, box-sizing and content-sizing, next/prev are done on best effort basis.
- Default implementation should work for 95% of the use cases.
- Alternatively you can use
scrollToIndex
ofscrollToLeft
.
- Snapping might not work as expected if smoothscroll is polyfill-ed.
<vue-horizontal :button="false">
<template>
<vue-horizontal :button="false">
<component-example class="component" v-for="i in [1,2,3,4,5,6,7,8]" :key="i">
<h3>Disabled {{i}}</h3>
</component-example>
</vue-horizontal>
</template>
Override with slots
<template>
<vue-horizontal>
<component-example class="component" v-for="i in [1,2,3,4,5,6,7,8]" :key="i">
<h3>Override {{ i }}</h3>
</component-example>
<template v-slot:btn-prev>
<button>Prev</button>
</template>
<template v-slot:btn-next>
<button>Next</button>
</template>
</vue-horizontal>
</template>
<style scoped>
button {
padding: 8px;
background: black;
color: white;
font-weight: 700;
}
</style>
Events
@prev @next
Emitted when prev or next are clicked.
<template>
<div>
<vue-horizontal @prev="onPrev" @next="onNext">
<component-example v-for="i in [1,2,3,4,5,6,7,8,9,10,11,12]" :key="i">
<h3>Event {{ i }}</h3>
</component-example>
</vue-horizontal>
<pre>{{prev}}</pre>
<pre>{{next}}</pre>
</div>
</template>
<script>
export default {
data() {
return {
prev: 'no-event',
next: 'no-event',
}
},
methods: {
onPrev() {
this.prev += '- clicked prev'
},
onNext() {
this.next += '- clicked next'
}
}
}
</script>
@scroll
For convenience, @scroll-debounce is called when mounted.
<template>
<div>
<vue-horizontal @scroll="onScroll" @scroll-debounce="onScrollDebounce">
<component-example v-for="i in [1,2,3,4,5,6,7,8,9,10,11,12]" :key="i">
<h3>Event {{ i }}</h3>
</component-example>
</vue-horizontal>
<pre>{{scroll}}</pre>
<pre>{{scrollDebounce}}</pre>
</div>
</template>
<script>
export default {
data() {
return {
scroll: 'no-event',
scrollDebounce: 'no-event',
}
},
methods: {
onScroll(data) {
this.scroll = data
},
onScrollDebounce(data) {
this.scrollDebounce = data
}
}
}
</script>
Methods
prev()
next()
- Scroll to the next/prev set of elements.
- Elements that are half visible, will not be scrolled past.
- Due to the varying nature of margins, padding, box-sizing and content-sizing, next/prev are done on best effort basis.
- Default implementation should work for 95% of the use cases.
- Alternatively you can use
scrollToIndex
ofscrollToLeft
.
- Snapping might not work as expected if smoothscroll is polyfill-ed.
<template>
<div>
<vue-horizontal ref="horizontal">
<component-example v-for="i in [1,2,3,4,5,6,7,8,9,10,11,12]" :key="i">
<h3>Event {{ i }}</h3>
</component-example>
</vue-horizontal>
<button @click="$refs.horizontal.prev()">Prev</button>
<button @click="$refs.horizontal.next()">Next</button>
</div>
</template>
<style scoped>
button {
padding: 4px 16px;
border-radius: 3px;
background: black;
color: white;
font-weight: 700;
margin: 24px 12px 0 0;
}
</style>
scrollToIndex()
- Scroll to the index of the elements in
vue-horizontal
.
<template>
<div>
<vue-horizontal ref="horizontal">
<component-example v-for="i in [0,1,2,3,4,5,6,7,8,9,10,11,12]" :key="i">
<h3>Event {{ i }}</h3>
</component-example>
</vue-horizontal>
<button v-for="i in [0,1,2,3,4]" @click="$refs.horizontal.scrollToIndex(i)">
{{i}}
</button>
</div>
</template>
<style scoped>
button {
padding: 4px 16px;
border-radius: 3px;
background: black;
color: white;
font-weight: 700;
margin: 24px 12px 0 0;
}
</style>
scrollToLeft()
- The amount of pixel to the left you want to scroll by.
- Snap settings
snap="start|end|center"
will prevent the scrolling if it snaps backwards. - Scroll behavior options
scrollToLeft(100)
default to 'smooth'scrollToLeft(100, 'smooth')
smooth scrollscrollToLeft(100, 'auto')
without smooth scroll
<template>
<div>
<vue-horizontal ref="horizontal">
<component-example v-for="i in [0,1,2,3,4,5,6,7,8,9,10,11,12]" :key="i">
<h3>Event {{ i }}</h3>
</component-example>
</vue-horizontal>
<button v-for="i in [0,100,150,300,1000,3000]" @click="$refs.horizontal.scrollToLeft(i)">
{{i}}
</button>
</div>
</template>
<style scoped>
button {
padding: 4px 16px;
border-radius: 3px;
background: black;
color: white;
font-weight: 700;
margin: 24px 12px 0 0;
}
</style>
refresh()
Update Vue Horizontal internal data required for rendering update after dom changes.
Vue Horizontal uses slot that can encompass any HTML passed in without parsing, thus you need to tell Vue Horizontal when you updated the dom. This is designed to prevent cyclical event loop that causes browser to hang.
<template>
<vue-horizontal ref="horizontal">
<div v-for="i in items"></div>
</vue-horizontal>
</template>
<script>
export default {
data() {
return {
items: [0,1,2,3]
}
},
methods: {
update() {
this.items.push(4,5,6,7,8)
this.$refs.horizontal.refresh()
}
}
}
</script>
Displacement
Displacement is a positive float number that you can override for a custom scroll displacement. Defaults to 1.0
.
Changing the value affects next/prev button and .next()
.prev()
function call as well.
<template>
<div>
<vue-horizontal :displacement="0.5">
<component-example v-for="i in [1,2,3,4,5,6,7,8,9,10,11,12]" :key="i">
<h3>50% {{ i }}</h3>
</component-example>
</vue-horizontal>
<vue-horizontal :displacement="1.75" style="margin-top: 24px">
<component-example v-for="i in [1,2,3,4,5,6,7,8,9,10,11,12,13,15,16,17,18,19]" :key="i">
<h3>175% {{ i }}</h3>
</component-example>
</vue-horizontal>
</div>
</template>
CSS
Overriding internal CCS with Vue deep selector (>>>
).
.v-hl-btn
.v-hl-btn
for both left and right button..v-hl-btn-prev
.v-hl-btn-next
<template>
<vue-horizontal class="this">
<component-example v-for="i in [1,2,3,4,5,6,7,8]" :key="i">
<h3>Button {{ i }}</h3>
</component-example>
</vue-horizontal>
</template>
<style scoped>
.this >>> .v-hl-btn {
filter: invert(1);
}
.this >>> .v-hl-btn-next {
transform: translateX(0);
}
.this >>> .v-hl-btn-prev {
top: 0;
}
.this >>> .v-hl-btn-prev svg {
margin: 0;
padding: 4px;
height: 30px;
width: 30px;
}
</style>
.v-hl-container
<template>
<vue-horizontal class="this">
<component-example v-for="i in [1,2,3,4,5,6,7,8]" :key="i">
<h3>Button {{ i }}</h3>
</component-example>
</vue-horizontal>
</template>
<style scoped>
.this >>> .v-hl-container {
background: black;
}
</style>
.vue-horizontal
The root div, has .vue-horizontal
as the only class with 2 CSS attributes.
Ensure you don't override them as they are used for nav button positioning.
position: relative;
display: flex;