JSFiddle - React, Tailwind, and code Playground
by Paul Leech
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="menu-wrapper">
<ul class="menu">
<li class="item">1</li>
<li class="item">2</li>
<li class="item">3</li>
<li class="item">4</li>
<li class="item">5</li>
<li class="item">6</li>
<li class="item">7</li>
<li class="item">8</li>
<li class="item">9</li>
<li class="item">10</li>
<li class="item">11</li>
<li class="item">12</li>
<li class="item">13</li>
</ul>
<div class="paddles">
<button class="left-paddle paddle hidden"/>
<button class="right-paddle paddle">
>
</button>
</div>
</div>
<div class="print" id="print-wrapper-size"><span></span> Wrapper / visible menu size</div>
<div class="print" id="print-menu-size"><span></span> Total menu size</div>
<div class="print" id="print-menu-invisible-size"><span></span> Invisible menu size</div>
<div class="print" id="print-menu-position"><span></span> Scroll position</div>
CSS
body {
margin: 0;
}
* {
padding: 0;
margin: 0;
}
.menu-wrapper {
position: relative;
max-width: 100vw;
width: 100vw;
height: 100px;
margin: 1em auto;
border: 1px solid black;
overflow-x: hidden;
overflow-y: hidden;
}
.menu {
height: 120px;
background: #f3f3f3;
box-sizing: border-box;
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
}
.item {
display: inline-block;
width: 200px;
height: 100%;
outline: 1px dotted gray;
padding: 1em;
box-sizing: border-box;
}
}
.paddles {
}
.paddle {
position: absolute;
top: 0;
bottom: 0;
width: 2rem;
opacity:0;
transition:1.5s;
cursor:pointer;
background: rgba(240,240,240,0.2);
}
.paddle:hover {
transition:0.5s;
background: rgba(240,240,240,0.8);
}
.left-paddle {
left: 0;
}
.right-paddle {
right: 0;
}
.hidden {
display: none;
}
.print {
margin: auto;
max-width: 500px;
}
span {
display: inline-block;
width: 100px;
}
}
JavaScript
// duration of scroll animation
var scrollDuration = 300;
// paddles
var leftPaddle = document.getElementsByClassName('left-paddle');
var rightPaddle = document.getElementsByClassName('right-paddle');
// get items dimensions
var itemsLength = $('.item').length;
var itemSize = $('.item').outerWidth(true);
// get some relevant size for the paddle triggering point
var paddleMargin = 20;
// get wrapper width
var getMenuWrapperSize = function() {
return $('.menu-wrapper').outerWidth();
}
var menuWrapperSize = getMenuWrapperSize();
// the wrapper is responsive
$(window).on('resize', function() {
menuWrapperSize = getMenuWrapperSize();
});
// size of the visible part of the menu is equal as the wrapper size
var menuVisibleSize = menuWrapperSize;
// get total width of all menu items
var getMenuSize = function() {
return itemsLength * itemSize;
};
var menuSize = getMenuSize();
// get how much of menu is invisible
var menuInvisibleSize = menuSize - menuWrapperSize;
// get how much have we scrolled to the left
var getMenuPosition = function() {
return $('.menu').scrollLeft();
};
// finally, what happens when we are actually scrolling the menu
$('.menu').on('scroll', function() {
// get how much of menu is invisible
menuInvisibleSize = menuSize - menuWrapperSize;
// get how much have we scrolled so far
var menuPosition = getMenuPosition();
var menuEndOffset = menuInvisibleSize - paddleMargin;
// show & hide the paddles
// depending on scroll position
if (menuPosition <= paddleMargin) {
$(leftPaddle).addClass('hidden');
$(rightPaddle).removeClass('hidden');
} else if (menuPosition < menuEndOffset) {
// show both paddles in the middle
$(leftPaddle).removeClass('hidden');
$(rightPaddle).removeClass('hidden');
} else if (menuPosition >= menuEndOffset) {
$(leftPaddle).removeClass('hidden');
$(rightPaddle).addClass('hidden');
}
// print important values
$('#print-wrapper-size span').text(menuWrapperSize);
...