JavaScript - window.scroll( behavior: 'smooth' ) 在 Safari 中不起作用

Posted

技术标签:

【中文标题】JavaScript - window.scroll( behavior: \'smooth\' ) 在 Safari 中不起作用【英文标题】:JavaScript - window.scroll( behavior: 'smooth' ) not working in SafariJavaScript - window.scroll( behavior: 'smooth' ) 在 Safari 中不起作用 【发布时间】:2018-12-16 04:54:52 【问题描述】:

正如标题所说,它在 Chrome 上运行良好。但在 Safari 中,它只是将页面设置到所需的顶部和左侧位置。这是预期的行为吗?有没有办法让它很好地工作?

【问题讨论】:

响应很晚 - 但行为不适用于 Safari 或 Edge。您必须实现自己的系统或使用库。 在 safari 错误跟踪器中有一个官方错误:bugs.webkit.org/show_bug.cgi?id=188043 和 bugs.webkit.org/show_bug.cgi?id=189907 【参考方案1】:

使用smootscroll polyfill(适用于所有浏览器的解决方案),易于应用且轻量级的依赖: https://github.com/iamdustan/smoothscroll

通过 npm 或 yarn 安装后,将其添加到您的 ma​​in .js、.ts 文件(首先执行的文件)

import smoothscroll from 'smoothscroll-polyfill';
// or if linting/typescript complains
import * as smoothscroll from 'smoothscroll-polyfill';

// kick off the polyfill!
smoothscroll.polyfill();

【讨论】:

在发生反应的情况下放在哪里?它在 app.js 中不起作用... @Richardson 它应该在那里工作,你得到什么错误? 它在 index 或 app.js 中不起作用是否在 react all 中测试过? 这对我有用,插入到index.js 文件中。我一直认为最好把这些配置放在index.js,因为App.js是一个组件。 @EugenSunic 使用我的功能效果很好,我唯一缺少的是 safari 的平滑滚动。我会传递你的建议并使用cdn。效果很好。【参考方案2】:

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");

【讨论】:

@George Daniel,感谢原生 JS 解决方案,尽管您可以通过在代码中添加一些内联 cmets 来改进您的答案。 Window.requestAnimationFrame() 应该使用而不是超时,因为性能优化并且它只在可见时运行.. 等等。blog.teamtreehouse.com/… 感谢@George Daniel,我做了一个小笔试图在这里用cmets描述你的函数过程codepen.io/gfcf14/pen/qBEMWJe @Mathi 你想做什么?建立一个不工作的代码笔。这是一个相当老的解决方案,但应该仍然有效。 @GeorgeDaniel 我也这样做,但它不起作用。我没有得到任何回应【参考方案3】:

更全面的平滑滚动方法列表见我的回答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;
&lt;button&gt;Scroll to y-position 500px in 1500ms&lt;/button&gt;

对于更复杂的情况,可以使用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>

【讨论】:

谢谢。还有机会添加缓动吗? @FredK 抱歉回复晚了,但我已经更新了答案。【参考方案4】:

最重要的解决方法弥补了 Safari 对行为的支持不足。

仍然需要检测何时需要解决方法。

这个小功能会检测浏览器是否支持平滑滚动。它在 Safari 上返回 false,在 Chrome 和 Firefox 上返回 true:

// 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 Technology Preview, Release 139 (Safari 15.4) 的测试显示支持scrollBehavior smooth,因此我们可能会在 15.4 中看到支持。

【讨论】:

【参考方案5】:

性能最流畅的解决方案,特别是如果你想合并缓动,是使用 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);

【讨论】:

绝对是迄今为止我见过的最好的解决方案。【参考方案6】:

适用于 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()
        ))
    
);

【讨论】:

@conner coling:当内容被延迟加载时,您的代码(我对其进行了一些更改)帮助了我,导致内容移位破坏了最终的滚动偏移位置。非常感谢!!!【参考方案7】:

结合George Daniel和terrymorse的答案,以下可用于所有浏览器对原生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);

【讨论】:

与所有其他答案一样,这不适用于 Safari 14.1.2 (MacOS 11.5.1) @BSUK:在 Safari 14.0.2 (MacOS 11.1) 上运行良好。 点击链接时出现此错误:TypeError: null is not an object (evalating 'element.getBoundingClientRect') @BSUK:我怀疑在这里const scrollToElem = document.getElementById("section");,scrollToElem 在你的情况下是空的。请确保您有一个 id="section" 的元素,我们想要滚动到该元素。或者确保添加正确的 id 选择器,以便 scrollToElem 不会为空。 非常感谢您的帮助!如果我对元素 ID 进行硬编码,它现在可以工作,但显然它必须根据单击的任何锚链接动态工作(如默认的平滑滚动行为)。我想我需要复习一下我的 javascript 并稍微开发一下这个功能。非常烦人的是 Safari 不仅仅支持这种开箱即用的 CSS 行为。【参考方案8】:

感谢 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()。

【讨论】:

以上是关于JavaScript - window.scroll( behavior: 'smooth' ) 在 Safari 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

JS滚动页面操作

ios软键盘将页面抵到上面后,关闭软键盘页面不回弹

从浏览器中删除地址栏(在 Android 上查看)

JavaScript介绍

javascript的题。

javascript JavaScript isset()等效: - JavaScript