平滑滚动,无需使用 jQuery
Smooth scroll without the use of jQuery
我正在编写一个页面,我只想在没有任何插件或框架干扰的情况下对UI使用原始JavaScript代码。
现在我正在努力寻找一种在没有 jQuery 的情况畅地滚动页面的方法。
JavaScript 中的原生浏览器平滑滚动是这样的:
// 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'
});
试试这个流畅的滚动演示,或者像这样的算法:
- 使用
self.pageYOffset
获取当前热门位置 - 获取元素的位置,直到要滚动到的位置:
element.offsetTop
- 做一个for循环到达那里,这将非常快,或者使用计时器使用
window.scrollTo
进行平滑滚动直到该位置
另请参阅此问题的其他流行答案。
安德鲁·约翰逊的原始代码:
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++;
}
}
相关链接:
- 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
算法
滚动元素需要随时间更改其scrollTop
值。对于给定的时间点,计算新的scrollTop
值。若要平滑地制作动画,请使用平滑步长算法进行插值。
计算scrollTop
如下:
var point = smooth_step(start_time, end_time, now);
var scrollTop = Math.round(start_top + (distance * point));
哪里:
-
start_time
是动画开始的时间; -
end_time
是动画何时结束(start_time + duration)
; -
start_top
是开头的scrollTop
值;和 -
distance
是所需结束值与起始值之间的差异(target - start_top)
。
可靠的解决方案应检测动画何时中断等。阅读我关于没有jQuery的平滑滚动的文章以了解详细信息。
演示
请参阅 JSFiddle。
实现
代码:
/**
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);
});
}
您可以使用新的滚动行为 CSS 属性。
例如,将以下行添加到您的 CSS 中。
html{
scroll-behavior:smooth;
}
这将产生原生平滑滚动功能。
在此处查看演示
所有现代浏览器都支持滚动行为属性。
阅读有关滚动行为的更多信息
我在这里做了一个没有jQuery的例子: 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;
}
}
}
现代浏览器支持CSS"滚动行为:平滑"属性。所以,我们甚至根本不需要任何Javascript。只需为"html"元素添加此元素,并使用常用的锚点和链接。滚动行为 MDN 文档
我最近开始在 jQuery 不是一种选择的情况下解决这个问题,所以我在这里记录我的解决方案只是为了后代。
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');
scroll()
函数使用揭示模块模式通过 scroll.To('id')
将目标元素的 id 传递给其scrolling()
函数,该函数设置scroller()
函数使用的值。
故障
在scrolling()
:
-
el
:目标 DOM 对象 -
elPos
: 通过elememtPosition()
返回一个函数,该函数在每次调用目标元素时给出目标元素相对于页面顶部的位置。 -
duration
:以毫秒为单位的转换时间。 -
increment
:将目标元素的起始位置分为 40 个步骤。 -
time
:设置每个步骤的计时。 -
prev
:目标元素在scroller()
中的先前位置。 -
E
:保持目标元素在scroller()
中的位置。
实际工作由 scroller()
函数完成,该函数继续调用自身(通过 setTimeout()
),直到目标元素位于页面顶部或页面无法再滚动。
每次调用scroller()
时,它都会检查目标元素的当前位置(保持在变量 E
中),如果这是> 1
或< -1
,并且页面仍然可滚动,则窗口移动 increment
像素 - 向上或向下,具体取决于E
是正值还是负值。 当E
既不是> 1
也不是< -1
,或者E
===prev
函数停止时。 我在完成时添加了 DOMElement.scrollTo()
方法,只是为了确保目标元素在窗口顶部爆炸(并不是说您会注意到它超出了一小部分像素!
scroller()
第 2 行的 if
语句通过对照其先前的位置检查E
(prev
)来检查页面是否正在滚动(在目标可能朝向页面底部并且页面无法进一步滚动的情况下)。
当E
接近零时,它下面的三元条件会减小increment
值。 这会阻止页面以一种方式超调,然后反弹回来以超过另一种方式,然后反弹以再次以乒乓球风格超越另一个,直到无限和更远。
如果您的页面高度超过 c.4000px,您可能需要增加三元表达式的第一个条件(此处为 +/-20)和/或设置increment
值的除数(此处为 40)中的值。
玩duration
,设置increment
的除数,以及三元条件的值scroller()
应该允许你定制函数以适应你的页面。
JSFiddle
注意:在最新版本的 Firefox 和 Chrome on Lubuntu 以及 Firefox、Chrome 和 IE on Windows8 中进行测试。
document.querySelector('your-element').scrollIntoView({behavior: 'smooth'});
如果要在页面顶部滚动,只需在顶部放置一个空元素,然后平滑滚动到该元素。
我做了这样的东西。我不知道它是否在IE8中工作。在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();
});
}
描述
-
pointer
— get element 和 chceck 如果它有属性 "href" 如果是,摆脱"#" -
elem
—不带"#"的指针变量 -
elemOffset
- "滚动到"元素与页面顶部的偏移量
使用以下平滑滚动可以正常工作:
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 = 滚动顶部
y = 用于滚动
的div 的 id
注意: 要使身体滚动,请给身体一个ID。
的解决方案。适用于大多数浏览器
document.getElementById("scrollHere").scrollIntoView({behavior: "smooth"});
文档
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>
JavaScript 中有许多不同的平滑滚动方法。下面列出了最常见的。
要在确切的时间内滚动到某个位置,可以使用window.requestAnimationFrame
,每次计算适当的当前位置。 当不支持requestAnimationFrame
时,setTimeout
可用于类似的效果。(要使用下面的函数滚动到特定元素,只需将位置设置为 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);
}
});
}
演示:
/*
@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>
对于更复杂的情况,可以使用 SmoothScroll.js 库,该库处理垂直和水平平滑滚动、在其他容器元素内滚动、不同的缓动行为、相对于当前位置滚动等。
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>
或者,您可以将选项对象传递给window.scroll
,该对象滚动到特定的 x 和 y 位置,window.scrollBy
从当前位置滚动一定量:
// 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'
});
演示:
<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>
如果只需要滚动到元素,而不是文档中的特定位置,则可以使用 Element.scrollIntoView
并将behavior
设置为 smooth
。
document.getElementById("elemID").scrollIntoView({
behavior: 'smooth'
});
演示:
<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>
现代浏览器支持 scroll-behavior
CSS 属性,该属性可用于使文档中的滚动变得平滑(无需 JavaScript)。 为此,可以通过为锚标记提供#
href
加上要滚动到的元素的id
来执行此操作。还可以为特定容器(如div
)设置 scroll-behavior
属性,使其内容平滑滚动。
演示:
html, body{
scroll-behavior: smooth;
}
<a href="#elem">Scroll To Element</a>
<div id="elem" style="margin: 500px 0px;">Div</div>
的变化:
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);
} );
这是对我有用的代码。
`$('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();
};
});
}
}
});
'
- jQuery滚动功能只工作一次
- jQuery滚动器插件修改
- 当元素可见时,jQuery滚动函数会触发一次
- jQuery滚动到聊天框底部
- 如何发现用户是否使用jQuery滚动到HTML容器的末尾
- 使用 jQuery 滚动窗格
- jQuery 滚动到锚点适用于移动设备,但不适用于桌面
- JQuery 滚动列表框模仿键盘导航
- JQuery 滚动到锚点行为奇怪
- jquery滚动功能无法正常工作
- JQuery滚动到底部
- 使用jQuery滚动获取距窗口顶部的距离
- jQuery滚动&锚初始位置
- 如何使用jQuery滚动到页面顶部
- 核心滚动标题面板+jQuery滚动
- iOS jquery滚动视图插件不会在iframe上滚动y
- 取消弹出jquery滚动事件
- jQuery滚动力不会滚动超过450px
- jQuery滚动出现问题
- Jquery滚动到基于数据属性的部分