ScrollShadow.js
Handle shadows for scroll overflow
by Bart Kalisz
HTML
<div class="wrapper js-scroll-shadow">
<ul class="list">
<li class="list__item">Item</li>
<li class="list__item">Item</li>
<li class="list__item">Item</li>
<li class="list__item">Item</li>
<li class="list__item">Item</li>
<li class="list__item">Item</li>
</ul>
</div>
<div id="buttons">
<button id="add-li">Add item</button>
<button id="remove-li">Remove item</button>
</div>
SCSS
body {
background: #20262e;
}
//---------------------------------------------------------------------
// Scroll Shadow
//---------------------------------------------------------------------
.scroll-shadow {
position: relative;
overflow: hidden;
&:before, &:after {
position: absolute;
left: 0;
right: 0;
display: block;
height: 8px;
border-radius: 100%;
content: " ";
opacity: 0;
transition: opacity .1s ease-out;
}
&:before {
top: -8px;
box-shadow: 0px -3px 10px 4px black;
}
&:after {
bottom: -8px;
box-shadow: 0px 3px 10px 4px black;
}
ul {
margin: 0 !important;
height: 100% !important;
width: 100% !important;
overflow: scroll !important;
-webkit-overflow-scrolling: thouch;
}
}
.scroll-shadow--top {
&:before { opacity: 1 }
}
.scroll-shadow--bottom {
&:after { opacity: 1 }
}
.scroll-shadow--both {
&:before { opacity: 1 }
&:after { opacity: 1 }
}
//---------------------------------------------------------------------
// Wrapper
//---------------------------------------------------------------------
.wrapper {
width: 250px;
height: 300px;
margin: 20px auto;
border-radius: 4px;
box-shadow: 0 0 1px 0 rgba(44, 54, 67, 0.1);
}
//---------------------------------------------------------------------
// List
//---------------------------------------------------------------------
.list {
padding: 0;
background: white;
list-style: none;
}
.list__item {
padding: 10px;
border-bottom: 1px solid rgba(219, 230, 236, 0.5);
color: #3b444f;
cursor: pointer;
font-family: arial;
font-size: 14px;
&:hover {
background: #fbfcfe;
}
}
//---------------------------------------------------------------------
// Buttons
//---------------------------------------------------------------------
#buttons {
margin-top: 20px;
text-align: center;
button {
width: 100px;
}
}
JavaScript
// https://remysharp.com/2010/07/21/throttling-function-calls
function throttle(fn, threshhold, scope) {
threshhold || (threshhold = 250);
var last,
deferTimer;
return function () {
var context = scope || this,
now = +new Date,
args = arguments;
if (last && now < last + threshhold) {
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
}
function ScrollShadow(el) {
this.el = el || document.querySelectorAll(".js-scroll-shadow")[0];
if (this.el) this.init();
}
ScrollShadow.prototype.init = function() {
this.el.className += " scroll-shadow";
this.elClasses = this.el.className;
this.ul = this.el.getElementsByTagName("UL")[0];
this.ulHeight = this.ul.clientHeight;
this.state = null;
this.update();
this.listen();
};
ScrollShadow.prototype.listen = function() {
this.ul.onscroll = throttle(this.update.bind(this), 50);
};
ScrollShadow.prototype.update = function() {
var ul = this.ul,
prevState = this.state,
nextState;
if (this.ulHeight === ul.scrollHeight) {
nextState = "none";
} else if (Math.ceil(ul.scrollTop) <= 0) {
nextState = "bottom"
} else if (ul.scrollHeight - Math.ceil(ul.scrollTop) <= this.ulHeight) {
nextState = "top";
} else {
nextState = "both";
}
if (prevState !== nextState) {
this.state = nextState;
this.el.className = this.elClasses.concat(" scroll-shadow--", nextState);
}
return nextState;
};
var scrollShadow = new ScrollShadow();
$("#add-li").on("click", function() {
$(".js-scroll-shadow ul").append("<li class='list__item'>Item</li>");
scrollShadow.update();
});
$("#remove-li").on("click", function() {
$(".js-scroll-shadow ul li").last().remove();
scrollShadow.update();
});