So, what you need to do (also what I think youtube is doing):
- Have a container(div) to keep all the buttons in them, with overflow hidden, to disallow scroll. Have a parent div to house the container div.
- Have two absolutely positioned arrow buttons on left and right of the buttons container.
- Scroll the container manually using css
transform: translateX(0px).
- Change the value of px to translate on click of those arrow buttons.
- Calculate the size of the container div, and parent div, calculate values of max, min scroll. While scrolling make sure value of translateX stays within bounds to avoid overscolling. Also show hide your arrow buttons too according to these values.
Here is a codesandbox with working demo.
Also adding the code below:
<template>
<div>
<nav class="navbar" ref="navbar">
<div class="left-arrow" v-if="translateX < 0">
<div class="left-arrow-button" v-on:click="moveLeft"><</div>
</div>
<div class="scroll-container" ref="scroll" :style="scrollStyle">
<div class="btn" v-for="(genre, index) in genres" :key="index">
{{ genre }}
</div>
</div>
<div class="right-arrow" v-if="translateX > minVal">
<div class="right-arrow-button" v-on:click="moveRight">></div>
</div>
</nav>
</div>
</template>
<script>
export default {
data() {
return {
genres: [
"GENRE 1",
"GENRE 2",
"GENRE 3",
"GENRE 4",
"GENRE 5",
"GENRE 6",
"GENRE 7",
"GENRE 8",
"REALITY TV",
"SPORTS",
"HORROR",
],
scrollWidth: 0,
navbarWidth: 0,
translateX: 0,
};
},
computed: {
scrollStyle: function () {
return {
transform: `translateX(${this.translateX}px)`,
};
},
minVal: function () {
return -(this.scrollWidth - this.navbarWidth);
},
},
mounted() {
this.getScrollWidth();
},
methods: {
getScrollWidth() {
this.scrollWidth = this.$refs.scroll.clientWidth;
this.navbarWidth = this.$refs.navbar.clientWidth;
// console.log(this.scrollWidth, this.navbarWidth);
},
moveRight() {
const newVal = this.translateX - this.navbarWidth / 2;
this.translateX = Math.max(this.minVal, newVal);
},
moveLeft() {
const newVal = this.translateX + this.navbarWidth / 2;
this.translateX = Math.min(0, newVal);
},
},
};
</script>
<style scoped>
.navbar {
display: flex;
flex-direction: row;
overflow: hidden;
position: relative;
/* width: 500px; */
}
.scroll-container {
display: flex;
}
.btn {
margin: 0 8px;
padding: 8px 16px;
background-color: grey;
border-radius: 16px;
}
.left-arrow {
position: absolute;
left: 0;
z-index: 1000;
height: 100%;
display: flex;
}
.left-arrow::after {
background: linear-gradient(to right, white 20%, rgba(255, 255, 255, 0) 80%);
height: 100%;
width: 50px;
content: "";
pointer-events: none;
}
.left-arrow-button {
display: flex;
align-items: center;
background-color: white;
cursor: pointer;
}
.right-arrow {
position: absolute;
right: 0;
z-index: 1000;
height: 100%;
display: flex;
}
.right-arrow::before {
background: linear-gradient(to left, white 20%, rgba(255, 255, 255, 0) 80%);
height: 100%;
width: 50px;
content: "";
pointer-events: none;
}
.right-arrow-button {
display: flex;
align-items: center;
background-color: white;
cursor: pointer;
}
</style>
I am using plain css and html tags, if you are using bootstrap or something like that for styling, you can easily update the code make use of that styling.
Also I have not implemented any animations to make the scrolling smooth, which I think youtube does. To keep the answer short and focused on a single problem, I have skipped that. You can try doing that yourself and if you face any problems, please ask a separate question for that.