JavaScript - window.scroll({ behavior: 'smooth' }) 在 Safari 中不工作
JavaScript - window.scroll({ behavior: 'smooth' }) not working in Safari
正如标题所说,它在 Chrome 上运行得非常好。但在 Safari 中,它只是将页面设置到所需的顶部和左侧位置。这是预期的行为吗?有没有办法让它很好地工作?
IE/Edge/Safari 不完全支持行为选项,因此您必须自己实现一些东西。我相信 jQuery 已经有了一些东西,但如果你没有使用 jQuery,这里有一个纯粹的 JavaScript 实现:
function SmoothVerticalScrolling(e, time, where) {
var eTop = e.getBoundingClientRect().top;
var eAmt = eTop / 100;
var curTime = 0;
while (curTime <= time) {
window.setTimeout(SVS_B, curTime, eAmt, where);
curTime += time / 100;
}
}
function SVS_B(eAmt, where) {
if(where == "center" || where == "")
window.scrollBy(0, eAmt / 2);
if (where == "top")
window.scrollBy(0, eAmt);
}
如果你需要水平滚动:
function SmoothHorizontalScrolling(e, time, amount, start) {
var eAmt = amount / 100;
var curTime = 0;
var scrollCounter = 0;
while (curTime <= time) {
window.setTimeout(SHS_B, curTime, e, scrollCounter, eAmt, start);
curTime += time / 100;
scrollCounter++;
}
}
function SHS_B(e, sc, eAmt, start) {
e.scrollLeft = (eAmt * sc) + start;
}
调用示例是:
SmoothVerticalScrolling(myelement, 275, "center");
使用smootscroll polyfill(适用于所有浏览器的解决方案),简单适用且轻量级依赖:
https://github.com/iamdustan/smoothscroll
通过 npm 或 yarn 安装后,将其添加到 main .js, .ts 文件(第一个执行的文件)
import smoothscroll from 'smoothscroll-polyfill';
// or if linting/typescript complains
import * as smoothscroll from 'smoothscroll-polyfill';
// kick off the polyfill!
smoothscroll.polyfill();
适用于 Safari 的简单 jQuery 修复:
$('a[href*="#"]').not('[href="#"]').not('[href="#0"]').click(function (t) {
if (location.pathname.replace(/^\//, "") == this.pathname.replace(/^\//, "") && location.hostname == this.hostname) {
var e = $(this.hash);
e = e.length ? e : $("[name=" + this.hash.slice(1) + "]"), e.length && (t.preventDefault(), $("html, body").animate({
scrollTop: e.offset().top
}, 600, function () {
var t = $(e);
if (t.focus(), t.is(":focus")) return !1;
t.attr("tabindex", "-1"), t.focus()
}))
}
});
性能最流畅的解决方案,特别是如果你想合并缓动是使用 requestAnimationFrame:
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
const step = (timestamp) => {
window.scrollBy(
0,
1, // or whatever INTEGER you want (this controls the speed)
);
requestAnimationFrame(step);
};
requestAnimationFrame(step);
如果你想稍后取消滚动,你需要引用你的 requestAnimationFrame(在你使用 requestAnimationFrame(step) 的任何地方都这样做):
this.myRequestAnimationFrame = requestAnimationFrame(step);
const cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
cancelAnimationFrame(this.myRequestAnimationFrame);
现在,如果您想对滚动使用缓动并在滚动操作之间设置超时怎么办?
创建一个包含 60 个元素的数组(requestAnimationFrame 通常每秒调用 60 次。从技术上讲,无论浏览器的刷新率是多少,但 60 是最常见的数字。)我们将非线性地填充这个数组然后使用这些数字来控制在 requestAnimationFrame:
的每一步滚动多少
let easingPoints = new Array(60).fill(0)
选择缓动函数。假设我们正在做三次缓出:
function easeCubicOut(t) {
return --t * t * t + 1;
}
创建一个虚拟数组并用通过缓动函数传输的数据填充它。您马上就会明白为什么我们需要这个:
// easing function will take care of decrementing t at each call (too lazy to test it at the moment. If it doesn't, just pass it a decrementing value at each call)
let t = 60;
const dummyPoints = new Array(60).fill(0).map(()=> easeCubicOut(t));
const dummyPointsSum = dummyPoints.reduce((a, el) => {
a += el;
return a;
}, 0);
使用每个 dummyPoint 比率的帮助映射 easingPoints 到 dummyPointsSum:
easingPoints = easingPoints.map((el, i) => {
return Math.round(MY_SCROLL_DISTANCE * dummyPoints[i] / dummyPointsSum);
});
在您的滚动功能中,我们将进行一些调整:
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
let i = 0;
const step = (timestamp) => {
window.scrollBy(
0,
easingPoints[i],
);
if (++i === 60) {
i = 0;
return setTimeout(() => {
this.myRequestAnimationFrame = requestAnimationFrame(step);
}, YOUR_TIMEOUT_HERE);
}
};
this.myRequestAnimationFrame = requestAnimationFrame(step);
上述变通办法弥补了 Safari 对行为支持的不足。
仍然需要检测何时需要解决方法。
这个小函数会检测浏览器是否支持平滑滚动。它 returns 在 Safari 上为假,在 Chrome 和 Firefox 上为真:
// returns true if browser supports smooth scrolling
const supportsSmoothScrolling = () => {
const body = document.body;
const scrollSave = body.style.scrollBehavior;
body.style.scrollBehavior = 'smooth';
const hasSmooth = getComputedStyle(body).scrollBehavior === 'smooth';
body.style.scrollBehavior = scrollSave;
return hasSmooth;
};
const pre = document.querySelector('pre');
// returns true if browser supports smooth scrolling
const supportsSmoothScrolling = () => {
const body = document.body;
const scrollSave = body.style.scrollBehavior;
body.style.scrollBehavior = 'smooth';
const hasSmooth = getComputedStyle(body).scrollBehavior === 'smooth';
body.style.scrollBehavior = scrollSave;
return hasSmooth;
};
const supported = supportsSmoothScrolling();
pre.innerHTML = `supported: ${ (supported) ? 'true' : 'false'}`;
<h3>
Testing if 'scrollBehavior smooth' is supported
</h3>
<pre></pre>
更新
Safari 技术预览版 139 (Safari 15.4) 的测试表明支持 scrollBehavior smooth
,因此我们可能希望在 15.4 中看到支持。
结合 and 的答案,以下可以用于所有浏览器的支持使用原生JavaScript。
如Chrome,Firefox支持CSS,scroll-behavior: smooth;
不支持属性的浏览器,我们可以在下面添加。
HTML:
<a onclick="scrollToSection(event)" href="#section">
Redirect On section
</a>
<section id="section">
Section Content
</section>
CSS:
body {
scroll-behavior: smooth;
}
JavaScript:
function scrollToSection(event) {
if (supportsSmoothScrolling()) {
return;
}
event.preventDefault();
const scrollToElem = document.getElementById("section");
SmoothVerticalScrolling(scrollToElem, 300, "top");
}
function supportsSmoothScrolling() {
const body = document.body;
const scrollSave = body.style.scrollBehavior;
body.style.scrollBehavior = 'smooth';
const hasSmooth = getComputedStyle(body).scrollBehavior === 'smooth';
body.style.scrollBehavior = scrollSave;
return hasSmooth;
};
function SmoothVerticalScrolling(element, time, position) {
var eTop = element.getBoundingClientRect().top;
var eAmt = eTop / 100;
var curTime = 0;
while (curTime <= time) {
window.setTimeout(SVS_B, curTime, eAmt, position);
curTime += time / 100;
}
}
function SVS_B(eAmt, position) {
if (position == "center" || position == "")
window.scrollBy(0, eAmt / 2);
if (position == "top")
window.scrollBy(0, eAmt);
}
感谢 T.Dayya,我结合了关于该主题的几个答案,这里是 ts 模块
扩展函数 scrollSmoothIntoView.
export default {}
declare global {
interface Element {
scrollSmoothIntoView(): void;
}
}
Element.prototype.scrollSmoothIntoView = function()
{
const t = 45;
const tstep = 6.425/t;
const dummyPoints = new Array(t).fill(0).map((t, i) => circ(i * tstep));
const dummyPointsSum = dummyPoints.reduce((a, el) => { a += el; return a;}, 0);
const _window: any = window;
const _elem: any = getScrollParent(this);
const scroll_distance: any = (this as any).offsetTop - (!_elem.parentElement ? _window.scrollY : 0);
let easingPoints = new Array(t).fill(0)
easingPoints = easingPoints.map((el, i) => {
return Math.round(scroll_distance * dummyPoints[i] / dummyPointsSum);
});
const requestAnimationFrame = _window.requestAnimationFrame ||
_window.mozRequestAnimationFrame ||
_window.webkitRequestAnimationFrame ||
_window.msRequestAnimationFrame;
let i = 0;
const step = (timestamp:any) => {
_elem.scrollBy(0, easingPoints[i]);
if (++i < t)
setTimeout(() => { requestAnimationFrame(step) }, 2);
};
window.requestAnimationFrame(()=>requestAnimationFrame(step));
}
function getScrollParent(element: any, includeHidden?: any):any {
var style = getComputedStyle(element);
var excludeStaticParent = style.position === "absolute";
var overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
if (style.position === "fixed") return document.body;
for (var parent = element; (parent = parent.parentElement);) {
style = getComputedStyle(parent);
if (excludeStaticParent && style.position === "static") {
continue;
}
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent;
}
return document.body;
}
function circ(t:any) {
return 1+Math.cos(3+t);
}
使用 html_element.scrollSmoothIntoView()。
有关更全面的平滑滚动方法列表,请参阅我的回答 here。
window.requestAnimationFrame
可用于在精确的时间内执行平滑滚动。
为了平滑的垂直滚动,可以使用以下函数。请注意,水平滚动可以以大致相同的方式完成。
/*
@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);
}
});
}
演示:
/*
@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);
}
});
}
document.querySelector('button').addEventListener('click', function(e){
scrollToSmoothly(500, 1500);
});
html, body {
height: 1000px;
}
<button>Scroll to y-position 500px in 1500ms</button>
对于更复杂的情况,可以使用 SmoothScroll.js library,它可以处理垂直和水平的平滑滚动、在其他容器元素内滚动、不同的缓动行为、相对于当前位置的滚动等等。它还支持大多数没有原生平滑滚动的浏览器。
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>
具有“ease-out”效果的另一种可能的解决方案。
受到之前给出的一些答案的启发,
一个关键区别在于使用“步速”而不是指定持续时间,我发现根据固定步速计算每步的长度会产生平滑的“ease-out”效果作为步数随着滚动接近目标点而增加。
希望下面的代码容易理解。
function smoothScrollTo(destination) {
//check if browser supports smooth scroll
if (window.CSS.supports('scroll-behavior', 'smooth')) {
window.scrollTo({ top: destination, behavior: 'smooth' });
} else {
const pace = 200;
let prevTimestamp = performance.now();
let currentPos = window.scrollY;
// @param: timestamp is a "DOMHightResTimeStamp", check on MDN
function step(timestamp) {
let remainingDistance = currentPos < destination ? destination - currentPos : currentPos - destination;
let stepDuration = timestamp - prevTimestamp;
let numOfSteps = pace / stepDuration;
let stepLength = remainingDistance / numOfSteps;
currentPos = currentPos < destination ? currentPos + stepLength : currentPos - stepLength;
window.scrollTo({ top: currentPos });
prevTimestamp = timestamp;
if (Math.floor(remainingDistance) >= 1) window.requestAnimationFrame(step);
}
window.requestAnimationFrame(step);
}
}
这是我从这个伟大的社区受益多年后对 SO 的第一次贡献。非常感谢建设性的批评。
正如标题所说,它在 Chrome 上运行得非常好。但在 Safari 中,它只是将页面设置到所需的顶部和左侧位置。这是预期的行为吗?有没有办法让它很好地工作?
IE/Edge/Safari 不完全支持行为选项,因此您必须自己实现一些东西。我相信 jQuery 已经有了一些东西,但如果你没有使用 jQuery,这里有一个纯粹的 JavaScript 实现:
function SmoothVerticalScrolling(e, time, where) {
var eTop = e.getBoundingClientRect().top;
var eAmt = eTop / 100;
var curTime = 0;
while (curTime <= time) {
window.setTimeout(SVS_B, curTime, eAmt, where);
curTime += time / 100;
}
}
function SVS_B(eAmt, where) {
if(where == "center" || where == "")
window.scrollBy(0, eAmt / 2);
if (where == "top")
window.scrollBy(0, eAmt);
}
如果你需要水平滚动:
function SmoothHorizontalScrolling(e, time, amount, start) {
var eAmt = amount / 100;
var curTime = 0;
var scrollCounter = 0;
while (curTime <= time) {
window.setTimeout(SHS_B, curTime, e, scrollCounter, eAmt, start);
curTime += time / 100;
scrollCounter++;
}
}
function SHS_B(e, sc, eAmt, start) {
e.scrollLeft = (eAmt * sc) + start;
}
调用示例是:
SmoothVerticalScrolling(myelement, 275, "center");
使用smootscroll polyfill(适用于所有浏览器的解决方案),简单适用且轻量级依赖: https://github.com/iamdustan/smoothscroll
通过 npm 或 yarn 安装后,将其添加到 main .js, .ts 文件(第一个执行的文件)
import smoothscroll from 'smoothscroll-polyfill';
// or if linting/typescript complains
import * as smoothscroll from 'smoothscroll-polyfill';
// kick off the polyfill!
smoothscroll.polyfill();
适用于 Safari 的简单 jQuery 修复:
$('a[href*="#"]').not('[href="#"]').not('[href="#0"]').click(function (t) {
if (location.pathname.replace(/^\//, "") == this.pathname.replace(/^\//, "") && location.hostname == this.hostname) {
var e = $(this.hash);
e = e.length ? e : $("[name=" + this.hash.slice(1) + "]"), e.length && (t.preventDefault(), $("html, body").animate({
scrollTop: e.offset().top
}, 600, function () {
var t = $(e);
if (t.focus(), t.is(":focus")) return !1;
t.attr("tabindex", "-1"), t.focus()
}))
}
});
性能最流畅的解决方案,特别是如果你想合并缓动是使用 requestAnimationFrame:
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
const step = (timestamp) => {
window.scrollBy(
0,
1, // or whatever INTEGER you want (this controls the speed)
);
requestAnimationFrame(step);
};
requestAnimationFrame(step);
如果你想稍后取消滚动,你需要引用你的 requestAnimationFrame(在你使用 requestAnimationFrame(step) 的任何地方都这样做):
this.myRequestAnimationFrame = requestAnimationFrame(step);
const cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
cancelAnimationFrame(this.myRequestAnimationFrame);
现在,如果您想对滚动使用缓动并在滚动操作之间设置超时怎么办?
创建一个包含 60 个元素的数组(requestAnimationFrame 通常每秒调用 60 次。从技术上讲,无论浏览器的刷新率是多少,但 60 是最常见的数字。)我们将非线性地填充这个数组然后使用这些数字来控制在 requestAnimationFrame:
的每一步滚动多少let easingPoints = new Array(60).fill(0)
选择缓动函数。假设我们正在做三次缓出:
function easeCubicOut(t) {
return --t * t * t + 1;
}
创建一个虚拟数组并用通过缓动函数传输的数据填充它。您马上就会明白为什么我们需要这个:
// easing function will take care of decrementing t at each call (too lazy to test it at the moment. If it doesn't, just pass it a decrementing value at each call)
let t = 60;
const dummyPoints = new Array(60).fill(0).map(()=> easeCubicOut(t));
const dummyPointsSum = dummyPoints.reduce((a, el) => {
a += el;
return a;
}, 0);
使用每个 dummyPoint 比率的帮助映射 easingPoints 到 dummyPointsSum:
easingPoints = easingPoints.map((el, i) => {
return Math.round(MY_SCROLL_DISTANCE * dummyPoints[i] / dummyPointsSum);
});
在您的滚动功能中,我们将进行一些调整:
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
let i = 0;
const step = (timestamp) => {
window.scrollBy(
0,
easingPoints[i],
);
if (++i === 60) {
i = 0;
return setTimeout(() => {
this.myRequestAnimationFrame = requestAnimationFrame(step);
}, YOUR_TIMEOUT_HERE);
}
};
this.myRequestAnimationFrame = requestAnimationFrame(step);
上述变通办法弥补了 Safari 对行为支持的不足。
仍然需要检测何时需要解决方法。
这个小函数会检测浏览器是否支持平滑滚动。它 returns 在 Safari 上为假,在 Chrome 和 Firefox 上为真:
// returns true if browser supports smooth scrolling
const supportsSmoothScrolling = () => {
const body = document.body;
const scrollSave = body.style.scrollBehavior;
body.style.scrollBehavior = 'smooth';
const hasSmooth = getComputedStyle(body).scrollBehavior === 'smooth';
body.style.scrollBehavior = scrollSave;
return hasSmooth;
};
const pre = document.querySelector('pre');
// returns true if browser supports smooth scrolling
const supportsSmoothScrolling = () => {
const body = document.body;
const scrollSave = body.style.scrollBehavior;
body.style.scrollBehavior = 'smooth';
const hasSmooth = getComputedStyle(body).scrollBehavior === 'smooth';
body.style.scrollBehavior = scrollSave;
return hasSmooth;
};
const supported = supportsSmoothScrolling();
pre.innerHTML = `supported: ${ (supported) ? 'true' : 'false'}`;
<h3>
Testing if 'scrollBehavior smooth' is supported
</h3>
<pre></pre>
更新
Safari 技术预览版 139 (Safari 15.4) 的测试表明支持 scrollBehavior smooth
,因此我们可能希望在 15.4 中看到支持。
结合
如Chrome,Firefox支持CSS,scroll-behavior: smooth;
不支持属性的浏览器,我们可以在下面添加。
HTML:
<a onclick="scrollToSection(event)" href="#section">
Redirect On section
</a>
<section id="section">
Section Content
</section>
CSS:
body {
scroll-behavior: smooth;
}
JavaScript:
function scrollToSection(event) {
if (supportsSmoothScrolling()) {
return;
}
event.preventDefault();
const scrollToElem = document.getElementById("section");
SmoothVerticalScrolling(scrollToElem, 300, "top");
}
function supportsSmoothScrolling() {
const body = document.body;
const scrollSave = body.style.scrollBehavior;
body.style.scrollBehavior = 'smooth';
const hasSmooth = getComputedStyle(body).scrollBehavior === 'smooth';
body.style.scrollBehavior = scrollSave;
return hasSmooth;
};
function SmoothVerticalScrolling(element, time, position) {
var eTop = element.getBoundingClientRect().top;
var eAmt = eTop / 100;
var curTime = 0;
while (curTime <= time) {
window.setTimeout(SVS_B, curTime, eAmt, position);
curTime += time / 100;
}
}
function SVS_B(eAmt, position) {
if (position == "center" || position == "")
window.scrollBy(0, eAmt / 2);
if (position == "top")
window.scrollBy(0, eAmt);
}
感谢 T.Dayya,我结合了关于该主题的几个答案,这里是 ts 模块 扩展函数 scrollSmoothIntoView.
export default {}
declare global {
interface Element {
scrollSmoothIntoView(): void;
}
}
Element.prototype.scrollSmoothIntoView = function()
{
const t = 45;
const tstep = 6.425/t;
const dummyPoints = new Array(t).fill(0).map((t, i) => circ(i * tstep));
const dummyPointsSum = dummyPoints.reduce((a, el) => { a += el; return a;}, 0);
const _window: any = window;
const _elem: any = getScrollParent(this);
const scroll_distance: any = (this as any).offsetTop - (!_elem.parentElement ? _window.scrollY : 0);
let easingPoints = new Array(t).fill(0)
easingPoints = easingPoints.map((el, i) => {
return Math.round(scroll_distance * dummyPoints[i] / dummyPointsSum);
});
const requestAnimationFrame = _window.requestAnimationFrame ||
_window.mozRequestAnimationFrame ||
_window.webkitRequestAnimationFrame ||
_window.msRequestAnimationFrame;
let i = 0;
const step = (timestamp:any) => {
_elem.scrollBy(0, easingPoints[i]);
if (++i < t)
setTimeout(() => { requestAnimationFrame(step) }, 2);
};
window.requestAnimationFrame(()=>requestAnimationFrame(step));
}
function getScrollParent(element: any, includeHidden?: any):any {
var style = getComputedStyle(element);
var excludeStaticParent = style.position === "absolute";
var overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
if (style.position === "fixed") return document.body;
for (var parent = element; (parent = parent.parentElement);) {
style = getComputedStyle(parent);
if (excludeStaticParent && style.position === "static") {
continue;
}
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent;
}
return document.body;
}
function circ(t:any) {
return 1+Math.cos(3+t);
}
使用 html_element.scrollSmoothIntoView()。
有关更全面的平滑滚动方法列表,请参阅我的回答 here。
window.requestAnimationFrame
可用于在精确的时间内执行平滑滚动。
为了平滑的垂直滚动,可以使用以下函数。请注意,水平滚动可以以大致相同的方式完成。
/*
@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);
}
});
}
演示:
/*
@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);
}
});
}
document.querySelector('button').addEventListener('click', function(e){
scrollToSmoothly(500, 1500);
});
html, body {
height: 1000px;
}
<button>Scroll to y-position 500px in 1500ms</button>
对于更复杂的情况,可以使用 SmoothScroll.js library,它可以处理垂直和水平的平滑滚动、在其他容器元素内滚动、不同的缓动行为、相对于当前位置的滚动等等。它还支持大多数没有原生平滑滚动的浏览器。
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>
具有“ease-out”效果的另一种可能的解决方案。
受到之前给出的一些答案的启发,
一个关键区别在于使用“步速”而不是指定持续时间,我发现根据固定步速计算每步的长度会产生平滑的“ease-out”效果作为步数随着滚动接近目标点而增加。
希望下面的代码容易理解。
function smoothScrollTo(destination) {
//check if browser supports smooth scroll
if (window.CSS.supports('scroll-behavior', 'smooth')) {
window.scrollTo({ top: destination, behavior: 'smooth' });
} else {
const pace = 200;
let prevTimestamp = performance.now();
let currentPos = window.scrollY;
// @param: timestamp is a "DOMHightResTimeStamp", check on MDN
function step(timestamp) {
let remainingDistance = currentPos < destination ? destination - currentPos : currentPos - destination;
let stepDuration = timestamp - prevTimestamp;
let numOfSteps = pace / stepDuration;
let stepLength = remainingDistance / numOfSteps;
currentPos = currentPos < destination ? currentPos + stepLength : currentPos - stepLength;
window.scrollTo({ top: currentPos });
prevTimestamp = timestamp;
if (Math.floor(remainingDistance) >= 1) window.requestAnimationFrame(step);
}
window.requestAnimationFrame(step);
}
}
这是我从这个伟大的社区受益多年后对 SO 的第一次贡献。非常感谢建设性的批评。