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();
});