Last active
November 18, 2018 18:10
-
-
Save rajivnarayana/cae8ae073377deb0af1bd6269200243a to your computer and use it in GitHub Desktop.
Scroll Snapping with Javascript.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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