I recently found this website that uses scroll snapping. I looked into it and found that CSS supports this. However, it looks like snapping happens after the user stops scrolling. The same applies with the answer to this question.
The next thing I tried was using window.scrollTo and react-scroll, but both of these weren't as smooth as the website I've linked as an example since the user could still "fight" the scrolling by scrolling in the other direction.
I want it to scroll snap when the user starts scrolling. How can I do this with CSS or JavaScript?
The developer you were looking at is using this js script if you ant to emulate it exactly https://alvarotrigo.com/fullPage/
If jQuery is an option, that would be the easiest solution with the best browser compatibility. You can use the "wheel" event listener to detect the direction of the scroll, and then use jQuery animate to scroll the window to the appropriate element. I've provided an example based on this GitHub repo: https://github.com/epranka/sections-slider.
(function($) {
var selector = ".section";
var direction;
var $slide;
var offsetTop;
var $slides = $(selector);
var currentSlide = 0;
var isAnimating = false;
var stopAnimation = function() {
setTimeout(function() {
isAnimating = false;
}, 300);
};
var bottomIsReached = function($elem) {
var rect = $elem[0].getBoundingClientRect();
return rect.bottom <= $(window).height();
};
var topIsReached = function($elem) {
var rect = $elem[0].getBoundingClientRect();
return rect.top >= 0;
};
document.addEventListener(
"wheel",
function(event) {
var $currentSlide = $($slides[currentSlide]);
if (isAnimating) {
event.preventDefault();
return;
}
direction = -event.deltaY;
if (direction < 0) {
// next
if (currentSlide + 1 >= $slides.length) {
return;
}
if (!bottomIsReached($currentSlide)) {
return;
}
event.preventDefault();
currentSlide++;
$slide = $($slides[currentSlide]);
offsetTop = $slide.offset().top;
isAnimating = true;
$("html, body").animate({
scrollTop: offsetTop
},
1000,
stopAnimation
);
} else {
// back
if (currentSlide - 1 < 0) {
return;
}
if (!topIsReached($currentSlide)) {
return;
}
event.preventDefault();
currentSlide--;
$slide = $($slides[currentSlide]);
offsetTop = $slide.offset().top;
isAnimating = true;
$("html, body").animate({
scrollTop: offsetTop
},
1000,
stopAnimation
);
}
}, {
passive: false
}
);
})(jQuery);
.section {
position: relative;
display: flex;
height: 100vh;
}
#section1 {
background: blue;
}
#section2 {
background: #ff8c42;
}
#section3 {
background: #6699cc;
}
#section4 {
background: #00b9ae;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="section1" class="section"></div>
<div id="section2" class="section"></div>
<div id="section3" class="section"></div>
<div id="section4" class="section"></div>
Related
How is it possible to get the smooth scroll working more than once (with JQuery:s scrollTop)?
var projectContainer = document.getElementsByClassName('projectContainer')[0];
var position = $(window).scrollTop();
$(window).scroll(function() {
var scroll = $(window).scrollTop();
if (scroll > position) {
$([document.documentElement, document.body]).animate({
scrollTop: $(".projectContainer").offset().top
}, 2000);
projectContainer.style.background = "orange";
$(window).bind("mousewheel", function() {
$("html, body").stop();
});
} else {
projectContainer.style.background = "white";
}
position = scroll;
});
.top {
height: 1000px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="top">Scroll</div>
<div class="projectContainer">hello</div>
I've tried this but it only works for the first time after running the script.
Thanks in advance!
I don't know if I'm doing it right but I want to scroll to the next div when the user scroll down and scroll to the previous div in the page when the user scroll up.
First of all I do this to test the scroll event and the animation of scrolling :
$(document).ready(function() {
$(window).scroll(function(){
$("html, body").animate({
scrollTop: 1000
}, 1000);
});
});
That's just a test but it work well.
Now, I want to differentiate the scroll down and the scroll up event to scroll to the next or the previous div so I search and I found some solutions like this :
$(document).ready(function() {
var lastPosition = $(window).scrollTop();
$(window).scroll(function(){
var newPosition = $(window).scrollTop();
if(lastPosition - newPosition > 0){
$("html, body").animate({
scrollTop: 1000
}, 1000);
}
else {
$("html, body").animate({
scrollTop: 0
}, 1000);
}
});
});
But it doesn't work...
I think the method to get the scroll down or the scroll up doesn't work in my case.
Do you have any solution to do this or maybe an alternative ?
Thank you.
In your case, the height of the body or your container should be set to the window height and the overflow of that should be set to hidden, because you want to scroll to the target Div by javascript.
by setting the overflow: hidden on the body or your container, you prevent
the window from scrolling on the page.
body {
background: #eee;
height: 100%;
overflow: hidden;
}
The next step is detecting the scrolling direction (Up/Down). You can
check it by the deltaY property of the scroll event.
Finally, get the next or previous Div and scroll to it.
The complete example is here
$(window).on('mousewheel DOMMouseScroll', function(event) {
var deltaY = event.originalEvent.deltaY || event.deltaY;
if (deltaY > 0) {
// Scrolled to Down: Next Div
} else if (deltaY < 0) {
// Scrolled to Up: Previous Div
}
});
You can see the completed code here:
<html>
<head>
<script src="https://code.jquery.com/jquery-3.4.1.min.js" type="text/javascript"></script>
<style type="text/css">
body {
background: #eee;
height: 100%;
overflow: hidden;
}
.section {
height: 600px;
}
.section.active {
background: #ccc;
}
</style>
</head>
<body>
<div class="active section">Section 1</div>
<div class="section">Section 2</div>
<div class="section">Section 3</div>
<script type="text/javascript">
$(window).on('mousewheel DOMMouseScroll',function(event) {
var deltaY = event.originalEvent.deltaY || event.deltaY,
$activeSection = $('.section.active'),
$container = $('body'),
containerOffset = $container[0].offsetTop,
$targetSection = null,
mustBeScroll = false;
if(deltaY > 0) { // Scrolled to Down: Next Div
if($activeSection.next('.section').length > 0) {
$targetSection = $activeSection.next('.section');
mustBeScroll = true;
}
} else if (deltaY < 0) { // Scrolled to Up: Previous Div
if ($activeSection.prev('.section').length > 0) {
$targetSection = $activeSection.prev('.section');
mustBeScroll = true;
}
}
if(mustBeScroll == true) {
$container.stop(false, true).animate({ scrollTop : $targetSection[0].offsetTop - containerOffset }, 500);
// Change background color
$activeSection.removeClass('active');
$targetSection.addClass('active');
}
});
</script>
</body>
</html>
I want to animate different sections of a web page only when they are scrolled into view using vanilla javascript. This is what my code looks like right now
<script>
let target = document.querySelector("#who-we-are");
let service = document.querySelector("#what-we-do");
function animateAboutUs() {
if (target.scrollIntoView) {
document.querySelector("#who").classList.add("fadeIn");
}
}
function animateServiceList() {
if (service.scrollIntoView) {
document.querySelector("#service").classList.add("fadeIn");
}
}
window.onscroll = function() {
animateAboutUs();
animateServiceList();
};
</script>
The problem with doing it like this is that once a user starts to scroll down the page the service section gets animated even when its yet to come into view.
What is the proper way to do animation only when the section is scrolled into view for multiple sections?
A modern solution would be to use Intersection Observer instead of listening to the scroll event.
First you define the observer:
var options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 0.1
}
var observer = new IntersectionObserver(callback, options);
Threshold of .1 means that the callback() function gets called as soon as 10% (or more) are visible. Adjust this as you see fit obviously.
If you omit the root option the browser viewport is used.
Then you observe items:
var target = document.querySelector('.scrollItems');
observer.observe(target);
Now, whenever the target meets a threshold specified for the IntersectionObserver, the callback is invoked.
var callback = function(entries, observer) {
entries.forEach(entry => {
// this loops through each element that is visible, add your classes here
entry.addClass('fadeIn');
});
}
Note: If you also need to support older browsers, there is a polyfill available.
var $animation_elements = $('.animation-element');
var $window = $(window);
function check_if_in_view() {
var window_height = $window.height();
var window_top_position = $window.scrollTop();
var window_bottom_position = (window_top_position + window_height);
$.each($animation_elements, function() {
var $element = $(this);
var element_height = $element.outerHeight();
var element_top_position = $element.offset().top;
var element_bottom_position = (element_top_position + element_height );
//check to see if this current container is within viewport
if ((element_bottom_position >= window_top_position) &&
(element_top_position <= window_bottom_position)) {
$element.addClass('in-view');
} else {
$element.removeClass('in-view');
}
});
}
Here is an other generic solution using querySelectorAll, getBoundingClientRect and eventListeners.
See the comments on the example below:
document.querySelectorAll('.section').forEach(section => {
const rect = section.getBoundingClientRect(); // get position of section
if(rect.top < document.body.scrollTop + window.innerHeight){ // check initial if a section is in view
section.classList.add('fadeIn');
} else {
window.addEventListener("scroll", addClass(section, rect)); // add eventlistener
}
});
function addClass(element, rect) {
const offset = 100; // set an offset to the needed scrollposition (in px)
let handler = () => {
if(rect.top < document.body.scrollTop + window.innerHeight - offset){ // check if scrollposition is reached
element.classList.add('fadeIn');
window.removeEventListener('scroll', handler); // remove eventlistener
console.log(`reached section ${element.id}`);
}
};
return handler;
}
.section {
height: 100vh;
color: transparent;
text-align: center;
font-size: 100px;
}
.section.fadeIn {
color: #000 !important;
}
#one { background-color: yellow }
#two { background-color: green }
#three { background-color: orange }
#four { background-color: lightblue }
#five { background-color: grey }
<div class="section" id="one">Faded In!</div>
<div class="section" id="two">Faded In!</div>
<div class="section" id="three">Faded In!</div>
<div class="section" id="four">Faded In!</div>
<div class="section" id="five">Faded In!</div>
I want to be able, when scrolling down, to go directly to the next div, and when scrolling up, to go directly to the previous div.
Here are my files with an example with two divs:
$(document).ready(function() {
var lastScrollTop = 0;
function findPos(obj) {
var curtop = 0;
if (obj.offsetParent) {
do {
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return [curtop];
}
}
$(window).scroll(function(event) {
var st = $(this).scrollTop();
if (st > lastScrollTop) {
$('html, body').animate({
scrollTop: $("#space_od").offset().top
}, 500);
} else {
$('html, body').animate({
scrollTop: $("#black_hole").offset().top
}, 500);
}
lastScrollTop = st;
});
});
body {
padding: 0;
margin: 0;
}
#black_hole {
background-image: url("black_hole.jpg");
background-position: center;
display: block;
height: 100vh;
width: 100%;
}
#space_od {
background-image: url("2001.png");
background-position: center;
display: block;
height: 100vh;
width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="black_hole">
</div>
<div id="space_od">
</div>
It seems to work fine when scrolling down the first time, but not when scrolling up, it seems to move a few pixels and then stop. Any ideas?
scrollTo is a class to add to the divs you need to scroll to:
<div id="black_hole" class="scrollTo">
</div>
<div id="space_od" class="scrollTo">
</div>
Js
var scrolling = false;
var currentPos = 0;
function scrollUp(){
if(!scrolling && currentPos > 0 ){
scrolling=true;
currentPos --;
var scrollToElement = $('.scrollTo')[currentPos];
$('html, body').animate({
scrollTop: $(scrollToElement).offset().top
}, 500, function(){
scrolling = false;
});
}
}
function scrollDown(){
if(!scrolling && currentPos < $('.scrollTo').length-1 ){
scrolling=true;
currentPos ++;
var scrollToElement = $('.scrollTo')[currentPos];
$('html, body').animate({
scrollTop: $(scrollToElement).offset().top
}, 500,function(){
scrolling = false;
});
}
}
$(document).ready(function() {
// Get the current position on load
for( var i = 0; i < $('.scrollTo').length; i++){
var elm = $('.scrollTo')[i];
if( $(document).scrollTop() >= $(elm).offset().top ){
currentPos = i;
}
}
$(document).bind('DOMMouseScroll', function(e){
if(e.originalEvent.detail > 0) {
scrollDown();
}else {
scrollUp();
}
return false;
});
$(document).bind('mousewheel', function(e){
if(e.originalEvent.wheelDelta < 0) {
scrollDown();
}else {
scrollUp();
}
return false;
});
});
So basically I'd like to remove the class from 'header' after the user scrolls down a little and add another class to change it's look.
Trying to figure out the simplest way of doing this but I can't make it work.
$(window).scroll(function() {
var scroll = $(window).scrollTop();
if (scroll <= 500) {
$(".clearheader").removeClass("clearHeader").addClass("darkHeader");
}
}
CSS
.clearHeader{
height: 200px;
background-color: rgba(107,107,107,0.66);
position: fixed;
top:200;
width: 100%;
}
.darkHeader { height: 100px; }
.wrapper {
height:2000px;
}
HTML
<header class="clearHeader"> </header>
<div class="wrapper"> </div>
I'm sure I'm doing something very elementary wrong.
$(window).scroll(function() {
var scroll = $(window).scrollTop();
//>=, not <=
if (scroll >= 500) {
//clearHeader, not clearheader - caps H
$(".clearHeader").addClass("darkHeader");
}
}); //missing );
Fiddle
Also, by removing the clearHeader class, you're removing the position:fixed; from the element as well as the ability of re-selecting it through the $(".clearHeader") selector. I'd suggest not removing that class and adding a new CSS class on top of it for styling purposes.
And if you want to "reset" the class addition when the users scrolls back up:
$(window).scroll(function() {
var scroll = $(window).scrollTop();
if (scroll >= 500) {
$(".clearHeader").addClass("darkHeader");
} else {
$(".clearHeader").removeClass("darkHeader");
}
});
Fiddle
edit: Here's version caching the header selector - better performance as it won't query the DOM every time you scroll and you can safely remove/add any class to the header element without losing the reference:
$(function() {
//caches a jQuery object containing the header element
var header = $(".clearHeader");
$(window).scroll(function() {
var scroll = $(window).scrollTop();
if (scroll >= 500) {
header.removeClass('clearHeader').addClass("darkHeader");
} else {
header.removeClass("darkHeader").addClass('clearHeader');
}
});
});
Fiddle
Pure javascript
Here's javascript-only example of handling classes during scrolling.
const navbar = document.getElementById('navbar')
// OnScroll event handler
const onScroll = () => {
// Get scroll value
const scroll = document.documentElement.scrollTop
// If scroll value is more than 0 - add class
if (scroll > 0) {
navbar.classList.add("scrolled");
} else {
navbar.classList.remove("scrolled")
}
}
// Use the function
window.addEventListener('scroll', onScroll)
#navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
height: 60px;
background-color: #89d0f7;
box-shadow: 0px 5px 0px rgba(0, 0, 0, 0);
transition: box-shadow 500ms;
}
#navbar.scrolled {
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.25);
}
#content {
height: 3000px;
margin-top: 60px;
}
<!-- Optional - lodash library, used for throttlin onScroll handler-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
<header id="navbar"></header>
<div id="content"></div>
Some improvements
You'd probably want to throttle handling scroll events, more so as handler logic gets more complex, in that case throttle from lodash lib comes in handy.
And if you're doing spa, keep in mind that you need to clear event listeners with removeEventListener once they're not needed (eg during onDestroy lifecycle hook of your component, like destroyed() for Vue, or maybe return function of useEffect hook for React).
Example throttling with lodash:
// Throttling onScroll handler at 100ms with lodash
const throttledOnScroll = _.throttle(onScroll, 100, {})
// Use
window.addEventListener('scroll', throttledOnScroll)
Add some transition effect to it if you like:
http://jsbin.com/boreme/17/edit?html,css,js
.clearHeader {
height:50px;
background:lightblue;
position:fixed;
top:0;
left:0;
width:100%;
-webkit-transition: background 2s; /* For Safari 3.1 to 6.0 */
transition: background 2s;
}
.clearHeader.darkHeader {
background:#000;
}
Its my code
jQuery(document).ready(function(e) {
var WindowHeight = jQuery(window).height();
var load_element = 0;
//position of element
var scroll_position = jQuery('.product-bottom').offset().top;
var screen_height = jQuery(window).height();
var activation_offset = 0;
var max_scroll_height = jQuery('body').height() + screen_height;
var scroll_activation_point = scroll_position - (screen_height * activation_offset);
jQuery(window).on('scroll', function(e) {
var y_scroll_pos = window.pageYOffset;
var element_in_view = y_scroll_pos > scroll_activation_point;
var has_reached_bottom_of_page = max_scroll_height <= y_scroll_pos && !element_in_view;
if (element_in_view || has_reached_bottom_of_page) {
jQuery('.product-bottom').addClass("change");
} else {
jQuery('.product-bottom').removeClass("change");
}
});
});
Its working Fine
Is this value intended? if (scroll <= 500) { ... This means it's happening from 0 to 500, and not 500 and greater. In the original post you said "after the user scrolls down a little"
In a similar case, I wanted to avoid always calling addClass or removeClass due to performance issues. I've split the scroll handler function into two individual functions, used according to the current state. I also added a debounce functionality according to this article: https://developers.google.com/web/fundamentals/performance/rendering/debounce-your-input-handlers
var $header = jQuery( ".clearHeader" );
var appScroll = appScrollForward;
var appScrollPosition = 0;
var scheduledAnimationFrame = false;
function appScrollReverse() {
scheduledAnimationFrame = false;
if ( appScrollPosition > 500 )
return;
$header.removeClass( "darkHeader" );
appScroll = appScrollForward;
}
function appScrollForward() {
scheduledAnimationFrame = false;
if ( appScrollPosition < 500 )
return;
$header.addClass( "darkHeader" );
appScroll = appScrollReverse;
}
function appScrollHandler() {
appScrollPosition = window.pageYOffset;
if ( scheduledAnimationFrame )
return;
scheduledAnimationFrame = true;
requestAnimationFrame( appScroll );
}
jQuery( window ).scroll( appScrollHandler );
Maybe someone finds this helpful.
For Android mobile $(window).scroll(function() and $(document).scroll(function() may or may not work. So instead use the following.
jQuery(document.body).scroll(function() {
var scroll = jQuery(document.body).scrollTop();
if (scroll >= 300) {
//alert();
header.addClass("sticky");
} else {
header.removeClass('sticky');
}
});
This code worked for me. Hope it will help you.
This is based of of #shahzad-yousuf's answer, but I only needed to compress a menu when the user scrolled down. I used the reference point of the top container rolling "off screen" to initiate the "squish"
<script type="text/javascript">
$(document).ready(function (e) {
//position of element
var scroll_position = $('div.mainContainer').offset().top;
var scroll_activation_point = scroll_position;
$(window).on('scroll', function (e) {
var y_scroll_pos = window.pageYOffset;
var element_in_view = scroll_activation_point < y_scroll_pos;
if (element_in_view) {
$('body').addClass("toolbar-compressed ");
$('div.toolbar').addClass("toolbar-compressed ");
} else {
$('body').removeClass("toolbar-compressed ");
$('div.toolbar').removeClass("toolbar-compressed ");
}
});
}); </script>