V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
manyfreebug
V2EX  ›  JavaScript

利用 Math.ceil 等近似方法是否会影响该滚动动画的精确性?

  •  
  •   manyfreebug · 2022-01-27 22:07:02 +08:00 · 2113 次点击
    这是一个创建于 1030 天前的主题,其中的信息可能已经有所发展或是发生改变。

    完整代码及动画运行效果:https://codepen.io/TristanBLG...

    代码中实现了滚动页面至相应元素的功能,有疑问的地方在于代码利用Math.ceilMath.round做了一些近似的处理,这是否会影响到页面滚动到对应元素的精确性?涉及到小数总是难以判断.

    比如使用Math.ceil处理后会比原始值大 0.x,如果要求动画精确一点,这 0.x 是这个例子中不需要考虑的吗? 为什么有些地方用Math.ceil?而有些地方又用了Math.round?

    做了近似处理的地方: 下面的这两处代码作了近似处理,可以知道大致知道它们加起来近似了多少吗? 0.几? 会不会近似处理后离精准位置多了好几 pixel,而不仅仅是 0.几?

    window.scroll(0, Math.ceil((time * (destinationOffsetToScroll - start)) + start))
    
    if(time >= 1 || Math.round(window.pageYOffset) === destinationOffsetToScroll) {}
    

    完整 JavaScript 代码:

    const scrollTo = function({target, duration = 200, callback} = {}){
      if(!target){
        console.error('scrollTo() => You must specify a target.')
        return false
      }
      const targetHref                  = target.getAttribute('href').replace( /^#/ , '')
      const destination                 = document.getElementById(targetHref)
      const start                       = window.pageYOffset
      const startTime                   = 'now' in window.performance ? performance.now() : new Date().getTime()
      const documentHeight              = Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight)
      const windowHeight                = window.innerHeight || document.documentElement.clientHeight || document.getElementsByTagName('body')[0].clientHeight
      const destinationOffset           = typeof destination === 'number' ? destination : destination.offsetTop
      const destinationOffsetToScroll   = Math.round(documentHeight - destinationOffset < windowHeight ? documentHeight - windowHeight : destinationOffset)
    
      if('requestAnimationFrame' in window === false) {
          window.scroll(0, destinationOffsetToScroll)
          if(callback) {
              callback()
          }
          return
      }
      
      const scroll = function() {
        const now   = 'now' in window.performance ? performance.now() : new Date().getTime()
        const time  = Math.min(1, ((now - startTime) / duration))
        
        window.scroll(0, Math.ceil((time * (destinationOffsetToScroll - start)) + start))
        
        if(time >= 1 || Math.round(window.pageYOffset) === destinationOffsetToScroll) {
          if(callback) {
            callback()
          }
          return
        }
    
        requestAnimationFrame(scroll)
      }
    
      requestAnimationFrame(scroll)
    };
    
    const navLink = Array.from(document.querySelectorAll('[href^="#"]'))
      navLink.forEach(el => {
        el.addEventListener('click', function(ev) {
          ev.preventDefault()
          ev.stopPropagation()
          scrollTo({
            duration: 300,
            target: ev.target
          });
        })
    })
    
    
    13 条回复    2022-01-28 16:14:21 +08:00
    learningman
        1
    learningman  
       2022-01-27 22:11:27 +08:00
    那你考虑浮点误差不(
    差不多就可以了
    thedrwu
        2
    thedrwu  
       2022-01-27 22:17:08 +08:00 via Android
    这是要登月吗
    manyfreebug
        3
    manyfreebug  
    OP
       2022-01-27 22:28:30 +08:00
    没有要登月,但直观上上不好接受 :(
    @thedrwu
    @learningman
    iNaru
        4
    iNaru  
       2022-01-27 22:30:22 +08:00
    不需要取整等操作,有时 0.1 就是一个像素的误差。
    manyfreebug
        5
    manyfreebug  
    OP
       2022-01-27 22:44:58 +08:00
    @iNaru 临界条件怎么判断?if(Math.round(window.pageYOffset === destinationOffsetToScroll)) , 如果不去整, 可能永远没有相等的机会
    darkengine
        6
    darkengine  
       2022-01-27 22:57:36 +08:00
    window.scroll(x-coord, y-coord)

    Parameters
    x-coord is the pixel along the horizontal axis of the document that you want displayed in the upper left.
    y-coord is the pixel along the vertical axis of the document that you want displayed in the upper left.

    根据文档这两个参数就是像素值,那么你这里再精确到小数点之后多少位有啥用,你不取整 API 内部也会帮你取整。
    iNaru
        7
    iNaru  
       2022-01-27 23:13:26 +08:00   ❤️ 1
    @manyfreebug 取差值的绝对值是否在范围内
    manyfreebug
        8
    manyfreebug  
    OP
       2022-01-27 23:36:18 +08:00
    @iNaru 这个绝对值的范围取在多少合适? Math.abs(destinationOffsetToScroll - window.pageYOffset) < 1 ? 看运行效果似乎是可以的, 但怎么可以直观地看出差距在 1px 之内是可以的. 没有可能是 1.几甚至更高才行吗?
    hallDrawnel
        9
    hallDrawnel  
       2022-01-27 23:42:51 +08:00
    不用太纠结,因为除了你的计算,渲染也是浮点数计算的。感官 OK 的前提下用最简单的方式最好。
    manyfreebug
        10
    manyfreebug  
    OP
       2022-01-27 23:58:33 +08:00
    @hallDrawnel 不纠结的话就好了 , 直接取个 30 :) 现在的问题是尽可能地精确点
    jinliming2
        11
    jinliming2  
       2022-01-28 01:48:09 +08:00 via iPhone   ❤️ 1
    round 、floor 、ceil 的使用场景:
    首先,你期望得到一个整数(比如在针对物理像素点做优化计算的时候,必须使用整数)才会要使用这些舍入方法。
    round 就是四舍五入,结果可能比原值大,也可能比原值小,但一定是最接近原值的。通常变化的值就用这个来舍入。
    而 ceil 和 floor ,一个是向上取整,一个是向下取整。通常用于边界条件的时候。比如容器大小固定,那么内容就得用 floor 来向下取整,不然就可能会把容器撑开或者折断截断导致布局出错。类似的,如果容器大小不固定,那就要考虑使用 ceil 来进行向上取整,原因和用 floor 是是一样的。

    如果你不是要手动根据密度之类的东西去针对物理像素优化的话,那完全没必要舍入,直接用小数,由浏览器渲染引擎来决定与物理像素的映射。

    另外,滚动的边界条件浏览器应该是有处理的,滚动到头得到的事件参数值和取到的目标滚动参数应该是一样的。
    libook
        12
    libook  
       2022-01-28 10:17:46 +08:00   ❤️ 1
    可以取到显示内容的总高度,根据 viewport 倍率来判断精确到一个像素需要小数点后精确几位,然后再动态决定如何处理数值。

    为什么一定要取近似整数,浮点数不可以吗?
    manyfreebug
        13
    manyfreebug  
    OP
       2022-01-28 16:14:21 +08:00
    @libook 浮点数不有误差嘛 :)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   924 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 20:31 · PVG 04:31 · LAX 12:31 · JFK 15:31
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.