Following this answer to a similar question Make position: fixed behavior like sticky (for Vue2), I have tried to implement it in my application.
The solution was a little bit buggy (in some cases it behaved oddly, especially when opening other tabs and coming back), so I decided to implement it using jQuery and it's working as expected.
Here is the working example:
<template>
<div>
<div class="recap">
<div class="inner" :style="recapStyle">
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ProductRecap',
data() {
return {
scrollY: null,
top: null,
bottom: null,
marginTop: 40,
recapStyle: {},
};
},
methods: {
updatePosition(scroll) {
// using jQuery to calculate amount
const offset = $(this.$el).offset().top;
const scrollAmount = offset - scroll;
const rectHeight = $(this.$el).find('.inner').outerHeight();
if (scrollAmount < this.top) {
let updatedTop = scroll - offset + this.top;
if ((scroll + rectHeight) < this.bottom) {
this.prevScroll = updatedTop;
} else {
updatedTop = this.prevScroll;
}
this.$set(this.recapStyle, 'top', `${updatedTop}px`);
} else {
this.$delete(this.recapStyle, 'top');
}
},
},
watch: {
scrollY(scrollUpdate) {
// call `updatePosition` on scroll
this.updatePosition(scrollUpdate);
},
},
mounted() {
// calculate header size (position: fixed) and add a fixed offset
this.top = $('#main-header').outerHeight() + this.marginTop;
// calculate height of the document (without the footer)
this.bottom = document.querySelector('.global-container').offsetHeight;
// update scrollY position
window.addEventListener('scroll', _.throttle(() => {
this.scrollY = Math.round(window.scrollY);
}, 20, { leading: true }));
},
};
</script>
However I'd like to find a solution that doesn't use jQuery to calculate the offset, so I headed to You Might Not Need jQuery, but if I just replace the offset part with the one that's suggested it's still a bit buggy.
$(el).offset();
Should become:
var rect = el.getBoundingClientRect();
{
top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft
}
So I replaced the line:
const offset = $(this.$el).offset().top;
with:
const rect = this.$el.getBoundingClientRect();
const offset = rect.top + document.body.scrollTop;
But the distance of the sidebar from the fixed header increases with the scroll: can anyone explain how to fix it?
Here is a working fiddle (slightly simplified): Fiddle
The short answer is to use these two lines (the first one is yours):
const rect = this.$el.getBoundingClientRect();
const offset = rect.top + window.pageYOffset;
The longer answer of course includes the thought process to achieve this result. I ran
console.log($(this.$el).offset + "");
On your fiddle at the relevant place to see how the offset function is implemented and got this:
function( options ) {
// Preserve chaining for setter
if ( arguments.length ) {
return options === undefined ?
this :
this.each( function( i ) {
jQuery.offset.setOffset( this, options, i );
} );
}
var rect, win,
elem = this[ 0 ];
if ( !elem ) {
return;
}
// Return zeros for disconnected and hidden (display: none) elements (gh-2310)
// Support: IE <=11+
// Running getBoundingClientRect on a
// disconnected node in IE throws an error
if ( !elem.getClientRects().length ) {
return { top: 0, left: 0 };
}
// Get document-relative position by adding viewport scroll to viewport-relative gBCR
rect = elem.getBoundingClientRect();
win = elem.ownerDocument.defaultView;
return {
top: rect.top + win.pageYOffset,
left: rect.left + win.pageXOffset
};
}
The solution is inspired from this line:
top: rect.top + win.pageYOffset,
I am developing an app, and have run into a really simple but frustrating issue. Essentially I have a moving element and I want to track when it moves over the mouse. So currently, if the pointer is in the middle of the screen and not moving while the box passes over it NO event will be triggered. Is there any type of mouse or pointer event I could where it would trigger an event when the box passes over it?
Thanks. The simple code example I wrote is below:
<html>
<head>
<script src="https://code.jquery.com/jquery-3.0.0.js"></script>
</head>
<body>
<div id="box" style="height:300px;width:300px;background-color:green;position:absolute;top:600px;"></div>
<script>
var box = document.getElementById('box');
box.addEventListener('onmouseover',function(e){
console.log('im being tagged!')
});
$('#box').animate({top: '1px'},3000);
</script>
</body>
</html>
The events I HAVE tried so far: onmouseover, mouseover, mouseenter, mousemove, pointerenter,pointerover
The pointer events are from a library called pep.
Since you're looking to know when the moving box element is colliding with the mouse, not vice versa, you'll want to set up a looping check for that collision. You'll need to calculate and retain the position of the mouse cursor and use document.elementFromPoint to check if the element over the cursor is the box:
$(document).ready(function() {
var MouseCoords = function(x, y) {
this.x = x;
this.y = y;
};
var mousecoords = new MouseCoords(0,0);
var isTagged = false;
$( document ).on( "mousemove", function( event ) {
mousecoords.x = event.pageX;
mousecoords.y = event.pageY;
});
$('#box').animate({top: '1px'},3000);
function isBoxOverMouse() {
var collidingElement = document.elementFromPoint(mousecoords.x, mousecoords.y);
if (collidingElement != null && collidingElement.id == "box") {
if (!isTagged) {
isTagged = true;
console.log("Tag!");
}
} else {
isTagged = false;
}
}
setInterval(isBoxOverMouse, 500);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="box" style="height:300px;width:300px;background-color:green;position:absolute;top:600px;"></div>
I have the loop set to fire every 500ms, but that can be changed if that's not your desired frequency. I also set an isTagged variable so the console doesn't report the same collision multiple times, so feel free to remove that if you want the collision reported at every checked interval.
Adapting the code from this great answer to detect element collision
I added a function to run while the animation is in progress. Like this
$('#box').animate({
top: '1px'
}, {
duration: 5000,
// check if box overlaps cursor while animating
progress: function(promise) {
isOverlapping(this);
}
});
See the jQuery API docs for more details
$(function() {
var cursorX, cursorY = 0;
// tracks mouse position and updates its x,y coordinates
$(document).on('mousemove', function(e) {
cursorX = e.pageX;
cursorY = e.pageY;
$("#cursor").text("[X: " + e.pageX + ", Y: " + e.pageY + "]");
isOverlapping("#box");
});
// detects if two elements overlap
var overlaps = (function() {
function getPositions(elem) {
var pos, width, height;
pos = $(elem).position();
width = $(elem).width();
height = $(elem).height();
return [
[pos.left, pos.left + width],
[pos.top, pos.top + height]
];
}
function comparePositions(p1, p2) {
var r1, r2;
r1 = p1[0] < p2[0] ? p1 : p2;
r2 = p1[0] < p2[0] ? p2 : p1;
return r1[1] > r2[0] || r1[0] === r2[0];
}
return function(b) {
// making cursor 2x2 pixels
var pos1 = [
[cursorX, cursorX + 2],
[cursorY, cursorY + 2]
],
pos2 = getPositions(b);
return comparePositions(pos1[0], pos2[0]) && comparePositions(pos1[1], pos2[1]);
};
})();
// updates status if the box overlaps the cursor
var isOverlapping = function(elem){
if (overlaps(elem)) {
$("#boxPos").text("Overlaps");
$("#boxPos").addClass("yes");
$("#boxPos").removeClass("no");
} else {
$("#boxPos").text("No overlap");
$("#boxPos").addClass("no");
$("#boxPos").removeClass("yes");
}
}
$('#box').animate({
top: '1px'
}, {
duration: 5000,
// check if box overlaps cursor while animating
progress: function(promise) {
isOverlapping(this);
}
});
});
#cursor,
#boxPos {
float: right;
display: block;
}
.yes {
background-color: green;
}
.no {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="box" style="height:150px;width:150px;background-color:green;position:absolute;top:600px;"></div>
<div id="cursor"></div>
<br/>
<div id="boxPos"></div>
<br/>
Since you are using JQuery, you can use the mousemove event handler attached to the HTML body element. This will give you the mouse position, which you can then use to determine when the mouse is intersecting the box.
<html>
<head>
<script src="https://code.jquery.com/jquery-3.0.0.js"></script>
</head>
<body>
<div id="box" style="height:300px;width:300px;background-color:green;position:absolute;top:600px;"></div>
<script>
$(document).ready(function() {
var mousePos = {x:0, y:0}
$('html').mousemove(function(event) {
mousePos.x = event.clientX
mousePos.y = event.clientY
})
$('#box').animate({top: '1px'},{
duration: 3000,
step: function() {
console.log(parseInt($('#box').css('top')) <= mousePos.y)
}
});
})
</script>
</body>
</html>
I am implementing a swipe effect but move position of upper div by calculating the x, y coordinates and then move the wrapper(upper most div) with screen width, so it looks like swipe effect.
Now my problem is I dont know which in the next div showing on the screen, I need to manipulate on that, is there any method I can get the current div id which is showing on the DIV.
Below is my JS Code
(function() {
var swipey = {
slideContainer: null, //<ul> element object that holds the image slides
wrapper: null, //meant for masking/clipping
slides: null, //array of all slides i.e <li> elements
distanceX: 0, //distance moved in X direction i.e left or right
startX: 0, //registers the initial touch co-ordinate
preferredWidth: 0, //dynamic variable to set width
preferredHeight: 0, //dynamic variable to set height
direction: "", //direction of movement
timer: null, //timer that set starts when touch starts
timerCounter: 0, //counter variable for timer
hasSwipeStarted: false, //boolen to chk whether touch has started
maxDistance: 0, //maximum distance in X direction that slide container can move
currentDistance: 0, //current distance moved by slide container through translate
//detect touch and then automatically assign events
isTouchSupported: 'ontouchstart' in window.document,
initSwipey: function() {
// alert('in initSwipey');
//scroll the window up to hide the address bar of the browser.
window.setTimeout(function() {
window.scrollTo(0, 1);
}, 100);
/*mapping touch events to mouse events. Automatic registration of event
based on the device. If touch enabled then touch event is registered.
and if desktop browser then mouse event is registered.*/
swipey.startEvent = swipey.isTouchSupported ? 'touchstart' : 'mousedown',
swipey.moveEvent = swipey.isTouchSupported ? 'touchmove' : 'mousemove',
swipey.endEvent = swipey.isTouchSupported ? 'touchend' : 'mouseup',
//get all the instances of the HTML elements
swipey.wrapper = document.getElementById("wrapper");
swipey.slideContainer = document.getElementById("sitePageContainer");
//swipey.slides = slideContainer.getElementsByTagName("li");
swipey.slides = document.getElementsByClassName("sitePage");
//for iPhone, the width and height
swipey.preferredWidth = window.innerWidth;
// alert("swipey.preferredWidth: " + swipey.preferredWidth);
swipey.preferredHeight = window.innerHeight; //510 for android
// alert("swipey.preferredHeight: " + swipey.preferredHeight);
//setting the width and height to our wrapper with overflow = hidden
swipey.wrapper.style.width = swipey.preferredWidth + "px";
// alert("swipey.wrapper.style.width: " + swipey.wrapper.style.width);
swipey.wrapper.style.height = swipey.preferredHeight + "px";
// alert("swipey.wrapper.style.height: " + swipey.wrapper.style.height);
//setting the width to our <ul> element which holds all the <li> elements
swipey.slideContainer.style.height = swipey.preferredHeight + "px";
// alert("swipey.slideContainer.style.height: " + swipey.slideContainer.style.height);
swipey.slideContainer.style.width = swipey.slides.length * swipey.preferredWidth + "px";
// alert("swipey.slideContainer.style.width: " + swipey.slideContainer.style.width);
//display the <ul> container now
swipey.slideContainer.style.display = "inline-block";
//setting width and height for <li> elements - the slides
for(var i=0;i<swipey.slides.length;i++)
{
swipey.slides[i].style.width = swipey.preferredWidth + "px";
swipey.slides[i].style.height = swipey.preferredHeight + "px";
}
//calculating the max distance of travel for Slide Container i.e <ul> element
swipey.maxDistance = swipey.slides.length * swipey.preferredHeight;
//initialize and assign the touch events
swipey.initEvents();
},
initEvents: function() {
//registering touch events to the wrapper
//$('#wrapper').bind(swipey.startEvent, swipey.startHandler );
$('#wrapper').bind(swipey.startEvent, function(e){
$('#wrapper').bind(swipey.moveEvent, swipey.moveHandler );
} );
$('#wrapper').bind(swipey.endEvent, swipey.endHandler );
//swipey.wrapper.addEventListener(swipey.startEvent, swipey.startHandler, false);
//swipey.wrapper.addEventListener(swipey.moveEvent, swipey.moveHandler, false);
//swipey.wrapper.addEventListener(swipey.endEvent, swipey.endHandler, false);
},
//funciton called when touch start event is fired i.e finger is pressed on the screen
startHandler: function(event) {
//create appropriate event object to read the touch/mouse co-ordinates
var eventObj = swipey.isTouchSupported ? event.touches[0] : event;
//stores the starting X co-ordinate when finger touches the device screen
swipey.startX = eventObj.pageX;
//timer is set on
swipey.timer = setInterval(function() {
swipey.timerCounter++;
}, 10);
swipey.hasSwipeStarted = true;
event.preventDefault(); //prevents the window from scrolling.
},
//funciton called when touch move event is fired i.e finger is dragged over the screen
moveHandler: function(event) {
if (swipey.hasSwipeStarted) {
//create appropriate event object to read the touch/mouse co-ordinates
var eventObj = swipey.isTouchSupported ? event.touches[0] : event;
swipey.distanceX = eventObj.pageX - swipey.startX;
//move the slide container along with the movement of the finger
swipey.slideContainer.style.webkitTransform = "translate3d(" + (swipey.distanceX + swipey.currentDistance) + "px, 0,0)";
}else{
swipey.startHandler(event);
}
},
//funciton called when touch end event is fired i.e finger is released from screen
endHandler: function(event) {
$('#wrapper').unbind(swipey.moveEvent, swipey.moveHandler );
clearInterval(swipey.timer); //timer is stopped
if(swipey.distanceX == 0) //if the intention is to tap on the image then open a link
{
var link_url = event.target.getAttribute("link"); //read the link from <img /> element
// window.open(link_url,"_blank");
}
else
{
if (swipey.distanceX > 0) {
swipey.direction = "right";
}
if (swipey.distanceX < 0) {
swipey.direction = "left";
}
//the following conditions have been discussed in details
if ((swipey.direction == "right" && swipey.currentDistance == 0) || (swipey.direction == "left" && swipey.currentDistance == -(swipey.maxDistance - swipey.preferredWidth))) {
swipey.comeBack();
}
else if (swipey.timerCounter < 30 && swipey.distanceX > 10) {
swipey.moveRight();
}
else if (swipey.timerCounter < 30 && swipey.distanceX < -10) {
swipey.moveLeft();
}
else if (swipey.distanceX <= -(swipey.preferredWidth / 2)) { //-160
swipey.moveLeft();
}
else if (swipey.distanceX >= (swipey.preferredWidth / 2)) { //160
swipey.moveRight();
}
else {
swipey.comeBack();
}
}
swipey.timerCounter = 0; //reset timerCounter
swipey.hasSwipeStarted = false; //reset the boolean var
swipey.distanceX = 0; //reset the distance moved for next iteration
//alert(event.screenX+"::"+event.screenY);
var element = document.elementFromPoint(event.screenX, event.screenY);
var $element = $(element);
alert($element.attr('id'));
$("div.fixHeader").hide();
$("#"+$element.attr('id')+" div.fixHeader").show();
},
moveLeft: function() {
swipey.currentDistance += -swipey.preferredWidth;
swipey.slideContainer.style.webkitTransitionDuration = 300 + "ms";
//using CSS3 transformations - translate3d function for movement
swipey.slideContainer.style.webkitTransform = "translate3d(" + swipey.currentDistance + "px, 0,0)";
},
moveRight: function() {
swipey.currentDistance += swipey.preferredWidth;
swipey.slideContainer.style.webkitTransitionDuration = 300 + "ms";
swipey.slideContainer.style.webkitTransform = "translate3d(" + swipey.currentDistance + "px, 0,0)";
},
comeBack: function() {
swipey.slideContainer.style.webkitTransitionDuration = 250 + "ms";
swipey.slideContainer.style.webkitTransitionTimingFunction = "ease-out";
swipey.slideContainer.style.webkitTransform = "translate3d(" + swipey.currentDistance + "px, 0,0)";
}
}; //end of swipey object
window.swipeyObj = swipey; //expose to global window object
})();
window.onload = function() {
swipeyObj.initSwipey();
} //invoke the init method to get started
Updates:
Below are structure, Now I am sliding sitePageContainer and sitePage appears on screen, now I want to know which div is showing on screen (partypicturespage1 or partypicturespage2)
<div id="wrapper">
<div id="sitePageContainer">
<div class="sitePage" id="partypicturespage1"></div>
<div class="sitePage" id="partypicturespage2"></div>
You can select your visible div the following way:
$("div.fixHeader:visible")
Update:
If all of your divs are visible then use a custom data-* attribute to keep track of the div that is currently in view. You can do it the following way:
$("div.fixHeader").attr('data-visible', true);
and select it the following way:
$("div.fixHeader[data-visible=true]");
and you remove the attribute so you don't select divs that are no longer in view:
$("div.fixHeader[data-visible=true]").attr('data-visible', false);
Update #2:
If you scroll the page by setting the container's left property, then determining which div is in view is done by the following calculation in pseudo code:
ceil(scrollContainer.left / divWidth) // if index starts from 1
floor(scrollContainer.left / divWidth) // if index starts from 0
I'm coding up a page where I only want to use raw JavaScript code for UI without any interference of plugins or frameworks.
And now I'm struggling with finding a way to scroll over the page smoothly without jQuery.
Native browser smooth scrolling in JavaScript is like this:
// scroll to specific values,
// same as window.scroll() method.
// for scrolling a particular distance, use window.scrollBy().
window.scroll({
top: 2500,
left: 0,
behavior: 'smooth'
});
// scroll certain amounts from current position
window.scrollBy({
top: 100, // negative value acceptable
left: 0,
behavior: 'smooth'
});
// scroll to a certain element
document.querySelector('.hello').scrollIntoView({
behavior: 'smooth'
});
Try this smooth scrolling demo, or an algorithm like:
Get the current top location using self.pageYOffset
Get the position of element till where you want to scroll to: element.offsetTop
Do a for loop to reach there, which will be quite fast or use a timer to do smooth scroll till that position using window.scrollTo
See also the other popular answer to this question.
Andrew Johnson's original code:
function currentYPosition() {
// Firefox, Chrome, Opera, Safari
if (self.pageYOffset) return self.pageYOffset;
// Internet Explorer 6 - standards mode
if (document.documentElement && document.documentElement.scrollTop)
return document.documentElement.scrollTop;
// Internet Explorer 6, 7 and 8
if (document.body.scrollTop) return document.body.scrollTop;
return 0;
}
function elmYPosition(eID) {
var elm = document.getElementById(eID);
var y = elm.offsetTop;
var node = elm;
while (node.offsetParent && node.offsetParent != document.body) {
node = node.offsetParent;
y += node.offsetTop;
} return y;
}
function smoothScroll(eID) {
var startY = currentYPosition();
var stopY = elmYPosition(eID);
var distance = stopY > startY ? stopY - startY : startY - stopY;
if (distance < 100) {
scrollTo(0, stopY); return;
}
var speed = Math.round(distance / 100);
if (speed >= 20) speed = 20;
var step = Math.round(distance / 25);
var leapY = stopY > startY ? startY + step : startY - step;
var timer = 0;
if (stopY > startY) {
for ( var i=startY; i<stopY; i+=step ) {
setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
leapY += step; if (leapY > stopY) leapY = stopY; timer++;
} return;
}
for ( var i=startY; i>stopY; i-=step ) {
setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
}
}
Related links:
https://www.sitepoint.com/smooth-scrolling-vanilla-javascript/
https://github.com/zengabor/zenscroll/blob/dist/zenscroll.js
https://github.com/cferdinandi/smooth-scroll/blob/master/src/js/smooth-scroll.js
https://github.com/alicelieutier/smoothScroll/blob/master/smoothscroll.js
Algorithm
Scrolling an element requires changing its scrollTop value over time. For a given point in time, calculate a new scrollTop value. To animate smoothly, interpolate using a smooth-step algorithm.
Calculate scrollTop as follows:
var point = smooth_step(start_time, end_time, now);
var scrollTop = Math.round(start_top + (distance * point));
Where:
start_time is the time the animation started;
end_time is when the animation will end (start_time + duration);
start_top is the scrollTop value at the beginning; and
distance is the difference between the desired end value and the start value (target - start_top).
A robust solution should detect when animating is interrupted, and more. Read my post about Smooth Scrolling without jQuery for details.
Demo
See the JSFiddle.
Implementation
The code:
/**
Smoothly scroll element to the given target (element.scrollTop)
for the given duration
Returns a promise that's fulfilled when done, or rejected if
interrupted
*/
var smooth_scroll_to = function(element, target, duration) {
target = Math.round(target);
duration = Math.round(duration);
if (duration < 0) {
return Promise.reject("bad duration");
}
if (duration === 0) {
element.scrollTop = target;
return Promise.resolve();
}
var start_time = Date.now();
var end_time = start_time + duration;
var start_top = element.scrollTop;
var distance = target - start_top;
// based on http://en.wikipedia.org/wiki/Smoothstep
var smooth_step = function(start, end, point) {
if(point <= start) { return 0; }
if(point >= end) { return 1; }
var x = (point - start) / (end - start); // interpolation
return x*x*(3 - 2*x);
}
return new Promise(function(resolve, reject) {
// This is to keep track of where the element's scrollTop is
// supposed to be, based on what we're doing
var previous_top = element.scrollTop;
// This is like a think function from a game loop
var scroll_frame = function() {
if(element.scrollTop != previous_top) {
reject("interrupted");
return;
}
// set the scrollTop for this frame
var now = Date.now();
var point = smooth_step(start_time, end_time, now);
var frameTop = Math.round(start_top + (distance * point));
element.scrollTop = frameTop;
// check if we're done!
if(now >= end_time) {
resolve();
return;
}
// If we were supposed to scroll but didn't, then we
// probably hit the limit, so consider it done; not
// interrupted.
if(element.scrollTop === previous_top
&& element.scrollTop !== frameTop) {
resolve();
return;
}
previous_top = element.scrollTop;
// schedule next frame for execution
setTimeout(scroll_frame, 0);
}
// boostrap the animation process
setTimeout(scroll_frame, 0);
});
}
You can use the new Scroll Behaviour CSS Property.
for example, add the below line to your CSS.
html{
scroll-behavior:smooth;
}
and this will result in a native smooth scrolling feature.
see demo here
All modern browsers support the scroll-behavior property.
Read More about Scroll behavior
I've made an example without jQuery here : http://codepen.io/sorinnn/pen/ovzdq
/**
by Nemes Ioan Sorin - not an jQuery big fan
therefore this script is for those who love the old clean coding style
#id = the id of the element who need to bring into view
Note : this demo scrolls about 12.700 pixels from Link1 to Link3
*/
(function()
{
window.setTimeout = window.setTimeout; //
})();
var smoothScr = {
iterr : 30, // set timeout miliseconds ..decreased with 1ms for each iteration
tm : null, //timeout local variable
stopShow: function()
{
clearTimeout(this.tm); // stopp the timeout
this.iterr = 30; // reset milisec iterator to original value
},
getRealTop : function (el) // helper function instead of jQuery
{
var elm = el;
var realTop = 0;
do
{
realTop += elm.offsetTop;
elm = elm.offsetParent;
}
while(elm);
return realTop;
},
getPageScroll : function() // helper function instead of jQuery
{
var pgYoff = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
return pgYoff;
},
anim : function (id) // the main func
{
this.stopShow(); // for click on another button or link
var eOff, pOff, tOff, scrVal, pos, dir, step;
eOff = document.getElementById(id).offsetTop; // element offsetTop
tOff = this.getRealTop(document.getElementById(id).parentNode); // terminus point
pOff = this.getPageScroll(); // page offsetTop
if (pOff === null || isNaN(pOff) || pOff === 'undefined') pOff = 0;
scrVal = eOff - pOff; // actual scroll value;
if (scrVal > tOff)
{
pos = (eOff - tOff - pOff);
dir = 1;
}
if (scrVal < tOff)
{
pos = (pOff + tOff) - eOff;
dir = -1;
}
if(scrVal !== tOff)
{
step = ~~((pos / 4) +1) * dir;
if(this.iterr > 1) this.iterr -= 1;
else this.itter = 0; // decrease the timeout timer value but not below 0
window.scrollBy(0, step);
this.tm = window.setTimeout(function()
{
smoothScr.anim(id);
}, this.iterr);
}
if(scrVal === tOff)
{
this.stopShow(); // reset function values
return;
}
}
}
Modern browsers has support for CSS "scroll-behavior: smooth" property. So, we even don't need any Javascript at all for this. Just add this for the "html" element, and use usual anchors and links.
scroll-behavior MDN docs
I recently set out to solve this problem in a situation where jQuery wasn't an option, so I'm logging my solution here just for posterity.
var scroll = (function() {
var elementPosition = function(a) {
return function() {
return a.getBoundingClientRect().top;
};
};
var scrolling = function( elementID ) {
var el = document.getElementById( elementID ),
elPos = elementPosition( el ),
duration = 400,
increment = Math.round( Math.abs( elPos() )/40 ),
time = Math.round( duration/increment ),
prev = 0,
E;
function scroller() {
E = elPos();
if (E === prev) {
return;
} else {
prev = E;
}
increment = (E > -20 && E < 20) ? ((E > - 5 && E < 5) ? 1 : 5) : increment;
if (E > 1 || E < -1) {
if (E < 0) {
window.scrollBy( 0,-increment );
} else {
window.scrollBy( 0,increment );
}
setTimeout(scroller, time);
} else {
el.scrollTo( 0,0 );
}
}
scroller();
};
return {
To: scrolling
}
})();
/* usage */
scroll.To('elementID');
The scroll() function uses the Revealing Module Pattern to pass the target element's id to its scrolling() function, via scroll.To('id'), which sets the values used by the scroller() function.
Breakdown
In scrolling():
el : the target DOM object
elPos : returns a function via elememtPosition() which gives the position of the target element relative to the top of the page each time it's called.
duration : transition time in milliseconds.
increment : divides the starting position of the target element into 40 steps.
time : sets the timing of each step.
prev : the target element's previous position in scroller().
E : holds the target element's position in scroller().
The actual work is done by the scroller() function which continues to call itself (via setTimeout()) until the target element is at the top of the page or the page can scroll no more.
Each time scroller() is called it checks the current position of the target element (held in variable E) and if that is > 1 OR < -1 and if the page is still scrollable shifts the window by increment pixels - up or down depending if E is a positive or negative value. When E is neither > 1 OR < -1, or E === prev the function stops. I added the DOMElement.scrollTo() method on completion just to make sure the target element was bang on the top of the window (not that you'd notice it being out by a fraction of a pixel!).
The if statement on line 2 of scroller() checks to see if the page is scrolling (in cases where the target might be towards the bottom of the page and the page can scroll no further) by checking E against its previous position (prev).
The ternary condition below it reduce the increment value as E approaches zero. This stops the page overshooting one way and then bouncing back to overshoot the other, and then bouncing back to overshoot the other again, ping-pong style, to infinity and beyond.
If your page is more that c.4000px high you might want to increase the values in the ternary expression's first condition (here at +/-20) and/or the divisor which sets the increment value (here at 40).
Playing about with duration, the divisor which sets increment, and the values in the ternary condition of scroller() should allow you to tailor the function to suit your page.
JSFiddle
N.B.Tested in up-to-date versions of Firefox and Chrome on Lubuntu, and Firefox, Chrome and IE on Windows8.
I've made something like this.
I have no idea if its working in IE8.
Tested in IE9, Mozilla, Chrome, Edge.
function scroll(toElement, speed) {
var windowObject = window;
var windowPos = windowObject.pageYOffset;
var pointer = toElement.getAttribute('href').slice(1);
var elem = document.getElementById(pointer);
var elemOffset = elem.offsetTop;
var counter = setInterval(function() {
windowPos;
if (windowPos > elemOffset) { // from bottom to top
windowObject.scrollTo(0, windowPos);
windowPos -= speed;
if (windowPos <= elemOffset) { // scrolling until elemOffset is higher than scrollbar position, cancel interval and set scrollbar to element position
clearInterval(counter);
windowObject.scrollTo(0, elemOffset);
}
} else { // from top to bottom
windowObject.scrollTo(0, windowPos);
windowPos += speed;
if (windowPos >= elemOffset) { // scroll until scrollbar is lower than element, cancel interval and set scrollbar to element position
clearInterval(counter);
windowObject.scrollTo(0, elemOffset);
}
}
}, 1);
}
//call example
var navPointer = document.getElementsByClassName('nav__anchor');
for (i = 0; i < navPointer.length; i++) {
navPointer[i].addEventListener('click', function(e) {
scroll(this, 18);
e.preventDefault();
});
}
Description
pointer—get element and chceck if it has attribute "href" if yes,
get rid of "#"
elem—pointer variable without "#"
elemOffset—offset of "scroll to" element from the top of the page
You can use
document.querySelector('your-element').scrollIntoView({behavior: 'smooth'});
If you want to scroll top the top of the page, you can just place an empty element in the top, and smooth scroll to that one.
With using the following smooth scrolling is working fine:
html {
scroll-behavior: smooth;
}
<script>
var set = 0;
function animatescroll(x, y) {
if (set == 0) {
var val72 = 0;
var val73 = 0;
var setin = 0;
set = 1;
var interval = setInterval(function() {
if (setin == 0) {
val72++;
val73 += x / 1000;
if (val72 == 1000) {
val73 = 0;
interval = clearInterval(interval);
}
document.getElementById(y).scrollTop = val73;
}
}, 1);
}
}
</script>
x = scrollTop
y = id of the div that is used to scroll
Note:
For making the body to scroll give the body an ID.
Here is my solution. Works in most browsers
document.getElementById("scrollHere").scrollIntoView({behavior: "smooth"});
Docs
document.getElementById("end").scrollIntoView({behavior: "smooth"});
body {margin: 0px; display: block; height: 100%; background-image: linear-gradient(red, yellow);}
.start {display: block; margin: 100px 10px 1000px 0px;}
.end {display: block; margin: 0px 0px 100px 0px;}
<div class="start">Start</div>
<div class="end" id="end">End</div>
There are many different methods for smooth scrolling in JavaScript. The most common ones are listed below.
To scroll to a certain position in an exact amount of time, window.requestAnimationFrame can be put to use, calculating the appropriate current position each time. setTimeout can be used to a similar effect when requestAnimationFrame is not supported. (To scroll to a specific element with the function below, just set the position to element.offsetTop.)
/*
#param pos: the y-position to scroll to (in pixels)
#param time: the exact amount of time the scrolling will take (in milliseconds)
*/
function scrollToSmoothly(pos, time) {
var currentPos = window.pageYOffset;
var start = null;
if(time == null) time = 500;
pos = +pos, time = +time;
window.requestAnimationFrame(function step(currentTime) {
start = !start ? currentTime : start;
var progress = currentTime - start;
if (currentPos < pos) {
window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos);
} else {
window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time));
}
if (progress < time) {
window.requestAnimationFrame(step);
} else {
window.scrollTo(0, pos);
}
});
}
Demo:
/*
#param time: the exact amount of time the scrolling will take (in milliseconds)
#param pos: the y-position to scroll to (in pixels)
*/
function scrollToSmoothly(pos, time) {
var currentPos = window.pageYOffset;
var start = null;
if(time == null) time = 500;
pos = +pos, time = +time;
window.requestAnimationFrame(function step(currentTime) {
start = !start ? currentTime : start;
var progress = currentTime - start;
if (currentPos < pos) {
window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos);
} else {
window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time));
}
if (progress < time) {
window.requestAnimationFrame(step);
} else {
window.scrollTo(0, pos);
}
});
}
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 300)">
Scroll To Div (300ms)
</button>
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 200)">
Scroll To Div (200ms)
</button>
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 100)">
Scroll To Div (100ms)
</button>
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 50)">
Scroll To Div (50ms)
</button>
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 1000)">
Scroll To Div (1000ms)
</button>
<div style="margin: 500px 0px;">
DIV<p/>
<button onClick="scrollToSmoothly(0, 500)">
Back To Top
</button>
<button onClick="scrollToSmoothly(document.body.scrollHeight)">
Scroll To Bottom
</button>
</div>
<div style="margin: 500px 0px;">
</div>
<button style="margin-top: 100px;" onClick="scrollToSmoothly(500, 3000)">
Scroll To y-position 500px (3000ms)
</button>
For more complex cases, the SmoothScroll.js library can be used, which handles smooth scrolling both vertically and horizontally, scrolling inside other container elements, different easing behaviors, scrolling relatively from the current position, and more.
var easings = document.getElementById("easings");
for(var key in smoothScroll.easing){
if(smoothScroll.easing.hasOwnProperty(key)){
var option = document.createElement('option');
option.text = option.value = key;
easings.add(option);
}
}
document.getElementById('to-bottom').addEventListener('click', function(e){
smoothScroll({yPos: 'end', easing: easings.value, duration: 2000});
});
document.getElementById('to-top').addEventListener('click', function(e){
smoothScroll({yPos: 'start', easing: easings.value, duration: 2000});
});
<script src="https://cdn.jsdelivr.net/gh/LieutenantPeacock/SmoothScroll#1.2.0/src/smoothscroll.min.js" integrity="sha384-UdJHYJK9eDBy7vML0TvJGlCpvrJhCuOPGTc7tHbA+jHEgCgjWpPbmMvmd/2bzdXU" crossorigin="anonymous"></script>
<!-- Taken from one of the library examples -->
Easing: <select id="easings"></select>
<button id="to-bottom">Scroll To Bottom</button>
<br>
<button id="to-top" style="margin-top: 5000px;">Scroll To Top</button>
Alternatively, you can pass an options object to window.scroll which scrolls to a specific x and y position and window.scrollBy which scrolls a certain amount from the current position:
// Scroll to specific values
// scrollTo is the same
window.scroll({
top: 2500,
left: 0,
behavior: 'smooth'
});
// Scroll certain amounts from current position
window.scrollBy({
top: 100, // could be negative value
left: 0,
behavior: 'smooth'
});
Demo:
<button onClick="scrollToDiv()">Scroll To Element</button>
<div style="margin: 500px 0px;">Div</div>
<script>
function scrollToDiv(){
var elem = document.querySelector("div");
window.scroll({
top: elem.offsetTop,
left: 0,
behavior: 'smooth'
});
}
</script>
If you only need to scroll to an element, not a specific position in the document, you can use Element.scrollIntoView with behavior set to smooth.
document.getElementById("elemID").scrollIntoView({
behavior: 'smooth'
});
Demo:
<button onClick="scrollToDiv()">Scroll To Element</button>
<div id="myDiv" style="margin: 500px 0px;">Div</div>
<script>
function scrollToDiv(){
document.getElementById("myDiv").scrollIntoView({
behavior: 'smooth'
});
}
</script>
Modern browsers support the scroll-behavior CSS property, which can be used to make scrolling in the document smooth (without the need for JavaScript). Anchor tags can be used for this by giving the anchor tag a href of # plus the id of the element to scroll to). You can also set the scroll-behavior property for a specific container like a div to make its contents scroll smoothly.
Demo:
html, body{
scroll-behavior: smooth;
}
Scroll To Element
<div id="elem" style="margin: 500px 0px;">Div</div>
Here's my variation:
let MenuItem = function ( _menuItem ) {
// I had a sticky header, so its height had to be taken into account when scrolling
let _header = document.querySelector('.site-header');
let _scrollToBlock = function( e, menuItem ) {
let id = menuItem.getAttribute('href'), // the href attribute stores the id of the block to which the scroll will be
headerHeight = _header.offsetHeight; // determine the height of the header
id = id.replace(/#/, ''); // remove the # sign from the id block
let elem = document.getElementById( id ), // define the element to which we will scroll
top = elem.getBoundingClientRect().top + window.scrollY - headerHeight; // determine the height of the scroll
window.scroll({
top: top,
left: 0,
behavior: 'smooth'
});
},
_addEvents = function() {
_menuItem.addEventListener('click', function (e){
e.preventDefault(); // Disable redirect on click
_scrollToBlock(e, _menuItem);
});
},
_init = function() {
_addEvents();
};
_init();
};
// Initialize the class MenuItem to all links with class .menu__item
document.querySelectorAll('.menu__item').forEach( function(item) {
new MenuItem(item);
} );
Here's the code that worked for me.
`$('a[href*="#"]')
.not('[href="#"]')
.not('[href="#0"]')
.click(function(event) {
if (
location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '')
&&
location.hostname == this.hostname
) {
var target = $(this.hash);
target = target.length ? target : $('[name=' + this.hash.slice(1) + ']');
if (target.length) {
event.preventDefault();
$('html, body').animate({
scrollTop: target.offset().top
}, 1000, function() {
var $target = $(target);
$target.focus();
if ($target.is(":focus")) {
return false;
} else {
$target.attr('tabindex','-1');
$target.focus();
};
});
}
}
});
`