Skip to content

Instantly share code, notes, and snippets.

@rajivnarayana
Last active November 18, 2018 18:10
Show Gist options
  • Save rajivnarayana/cae8ae073377deb0af1bd6269200243a to your computer and use it in GitHub Desktop.
Save rajivnarayana/cae8ae073377deb0af1bd6269200243a to your computer and use it in GitHub Desktop.
Scroll Snapping with Javascript.
const goLeft = function(containerId) {
const container = document.getElementById(containerId);
const scrollLeft = container.scrollLeft;
const containerWidth = container.getBoundingClientRect().width
const childWidths = Array.prototype.map.call(container.children, (child => child.getBoundingClientRect().width))
const accumulatedWidths = childWidths.map((width, index, arr) => arr.slice(0, index + 1).reduce((x, y) => x+y, 0));
const childrenPastLeftEdge = accumulatedWidths.filter(width => width >= scrollLeft);
const newScrollLeft = Math.max(0, childrenPastLeftEdge.shift() - containerWidth);
scrollHorizontallyBy(container, newScrollLeft - container.scrollLeft, 300);
}
const goRight = function(containerId) {
const container = document.getElementById(containerId);
const scrollLeft = container.scrollLeft;
const containerWidth = container.getBoundingClientRect().width
const childWidths = Array.prototype.map.call(container.children, (child => child.getBoundingClientRect().width))
const accumulatedWidths = childWidths.map((width, index, arr) => arr.slice(0, index + 1).reduce((x, y) => x+y, 0));
const childrenTillRightEdge = accumulatedWidths.filter(width => width <= (scrollLeft + containerWidth));
const newScrollLeft = Math.min(accumulatedWidths.pop() - containerWidth, childrenTillRightEdge.pop());
scrollHorizontallyBy(container, newScrollLeft - container.scrollLeft, 300);
}
function scrollHorizontallyBy(element, deltaX, duration) {
let start = element.scrollLeft,
currentTime = 0,
increment = 20;
var animateScroll = function() {
currentTime += increment;
var val = easeInOutQuad(currentTime, start, deltaX, duration);
element.scrollLeft = val;
if (currentTime < duration) {
setTimeout(animateScroll, increment);
}
};
animateScroll();
}
function debounce(fn, interval) {
let timeoutId = null;
return function() {
const that = this;
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(fn.bind(that, arguments), interval);
}
}
//t = current time
//b = start value
//c = change in value
//d = duration
function easeInOutQuad(t, b, c, d) {
t /= d / 2;
if (t < 1) return c / 2 * t * t + b;
t--;
return -c / 2 * (t * (t - 2) - 1) + b;
}
/**
* Additional code to determine if the scroll is at ends.
*/
document.addEventListener("DOMContentLoaded", function() {
const container = document.getElementById("container");
onScrolled = debounce( _ => {
if (container.scrollLeft == 0) {
console.log("Left end reached.");
} else if (Math.abs(container.scrollLeft + container.getBoundingClientRect().width - container.scrollWidth) < 2) {
console.log("Right end reached.");
} else {
//console.log("Not at ends.");
}
}, 100);
container.addEventListener('scroll', onScrolled);
});
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>
.scroll-snap {
display: flex;
overflow-x: scroll;
background-color: lightskyblue;
}
.scroll-snap div {
color: indigo;
font-family: fantasy;
display: inline-block;
padding: 1.5em;
white-space: nowrap;
}
.controls {
display: flex;
justify-content: space-between;
margin-top : 10px;
}
#pager.scroll-snap div {
min-width: calc(100% - 20px);
padding-left : 10px;
padding-right : 10px;
}
@media screen and (max-width: 600px) {
.desktop {
display : none;
}
}
</style>
</head>
<script src="./index.js"></script>
<body>
<h3>Horizontal Scroll Snapping</h3>
<h4 class="desktop">View in a mobile browser</h4>
<div class="scroll-snap" id="container">
<div>TOI</div>
<div>Yahoo Cricket</div>
<div>CNN</div>
<div>Prime Video</div>
<div>Github</div>
<div>Netflix</div>
<div>Hotstar</div>
<div>ESPN CricInfo</div>
<div>Twitter</div>
<div>Youtube</div>
</div>
<div class="controls">
<button onclick="goLeft('container')">Scroll Left</button>
<button onclick="goRight('container')">Scroll Right</button>
</div>
<p>
Touch friendly demo of a scrolling functionality that snaps its children when navigated using the Left and Right buttons below.
</p>
<p>This should be replace with out of the box CSS only <code>scroll-snap</code> functionality once it becomes widely <a href="https://caniuse.com/#feat=css-snappoints">available</a> across the browsers.</p>
<p style="margin-top: 2em">
This can be also used as a pager when the items are exactly as big as the parent.
</p>
<div class="scroll-snap" id="pager">
<div>TOI</div>
<div>Yahoo Cricket</div>
<div>CNN</div>
<div>Prime Video</div>
<div>Github</div>
<div>Netflix</div>
<div>Hotstar</div>
<div>ESPN CricInfo</div>
<div>Twitter</div>
<div>Youtube</div>
</div>
<div class="controls">
<button onclick="goLeft('pager')">Prev Page</button>
<button onclick="goRight('pager')">Next Page</button>
</div>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment