当位置不是整数时平滑过渡 [英] Smooth transition when position is not an integer

查看:24
本文介绍了当位置不是整数时平滑过渡的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试显示某种时间表,我的目标是让它顺利刷新.

我设法使用 css 变换属性获得了比绝对定位更好的东西,但我不太高兴,因为有一些闪烁(尤其是在背景较暗时).

var background = document.querySelector('#background');var position = document.querySelector('#position');var transform = document.querySelector('#transform');var backgroundColor, borderColorbackground.addEventListener('change', e => {backgroundColor = e.target.checked ?'#333333':'白色'position.style.backgroundColor = backgroundColortransform.style.backgroundColor = backgroundColor});让电流 = 0让步 = 30for (让 i = 0; i <300; i++) {for (让 j = 0; j <2; j++) {var element = document.createElement('div')element.style.position = '绝对'element.style.height = '50px'element.style.width = 步长 + 'px'如果(j){element.style.left = 当前 + 'px'} 别的 {element.style.left = 0 + 'px'element.style.transform = 'translateX(' + current + 'px)'}element.style['border-left'] = '1px 灰色实心'如果 (j)position.appendChild(元素)别的transform.appendChild(元素)}当前 += 步骤}设置间隔(刷新,50);让初始化 = 0函数刷新(){初始化 -= 0.2让当前 = initfor (var i = 0; i < position.children.length; i++) {var c = position.children[i];c.style.left = 当前 + 'px'当前 += 步骤}当前 = 初始化for (var i = 0; i 

<身体><div><input type="checkbox" id="background" label="Dark"><label>深色</label>

<span>使用css位置<span><div id="position" style="width:100%;height:50px;margin-bottom:1em;"></div><span>使用css变换<span><div id="transform" style="width:100%;height:50px;"></div></html>

有没有更好的方法来做到这一点?

解决方案

动画闪烁的原因是它以 20fps (1000ms/50) 的速度转换得很慢.除了低 fps 之外,使用 setInterval 也不能保证每次都会调用回调函数(参见 此处为示例).为了让它更流畅,只需将刷新率增加到每秒 60 次,使用 requestAnimationFrame 而不是 setInterval 来动画对象,并增加每帧的平移值,以便动画不那么断断续续(想象一下,每 3 帧仅移动 .2 像素).

您正在使用 300 个 div 创建时间轴动画并同时为 300 个 div 设置动画.这可能会贵一点.为了简化你的动画,你可以简单地创建足够数量的 div 来适应整个容器加一个.然后,您只需要翻译直到最左边的 div 消失,然后再播放动画.当它实际上不是连续动画时,它会产生一种连续动画的错觉.另一种更高效的方法是只为 div 的容器(即包装器)设置动画.

如果您可以改变 positiontransform 来创建动画,请始终选择 transform.尝试阅读 CSS Tricks 中的链接以及由保罗爱尔兰 在这里.

此外,您不应该使用 setInterval 来制作动画;使用 requestAnimationFrame 代替.显然,还有其他方法.

使用JS

最后,这是一个同时使用 setIntervalrequestAnimationFrame 的工作示例:

window.onload = (() => {var background = document.querySelector('#background')var interval = document.querySelector('#setInterval')var intervalFast = document.querySelector('#setIntervalFast')var raq = document.querySelector('#raq')var raqFast = document.querySelector('#raqFast')var backgroundColor, borderColorbackground.addEventListener('change', e => {backgroundColor = e.target.checked ?'#333333':'白色'interval.style.backgroundColor = backgroundColorintervalFast.style.backgroundColor = backgroundColorraq.style.backgroundColor = 背景颜色raqFast.style.backgroundColor = backgroundColor})让 intervalDocFrag = document.createDocumentFragment()让 intervalFastDocFrag = document.createDocumentFragment()让 raqDocFrag = document.createDocumentFragment()让 raqFastDocFrag = document.createDocumentFragment()让电流 = 0让步 = 30let divNeeded = Math.ceil(interval.getBoundingClientRect().width/30) + 1//计算一个容器需要多少个 div + 1;30 是 div 的宽度(29px + 1px 的左边框)for (让 i = 0; i < divNeeded; i++) {for (让 j = 0; j <4; j++) {var element = document.createElement('div')element.style.position = '绝对'element.style.height = '50px'element.style.width = 步长 + 'px'element.style.left = 当前 + 'px'element.style['border-left'] = '1px 灰色实心'如果 (j === 0)intervalDocFrag.appendChild(元素)否则如果 (j === 1)raqDocFrag.appendChild(元素)否则如果 (j === 2)intervalFastDocFrag.appendChild(元素)否则如果 (j === 3)raqFastDocFrag.appendChild(元素)}当前 += 步骤}interval.appendChild(intervalDocFrag)intervalFast.appendChild(intervalFastDocFrag)raq.appendChild(raqDocFrag)raqFast.appendChild(raqFastDocFrag)让 intervalTranslateSlowValue = 0函数 intervalSlowAnimation() {if (Math.floor(intervalTranslateSlowValue) === -30) {intervalTranslateSlowValue = 0//重置动画以创建无尽的时间轴动画幻觉} 别的 {intervalTranslateSlowValue -= 0.064//从 0.2 * 16/50 得到}for(让interval.children的孩子){child.style.transform = `translateX(${intervalTranslateSlowValue}px)`}}让 intervalTranslateFastValue = 0函数 intervalFastAnimation() {if (Math.floor(intervalTranslateFastValue) === -30) {intervalTranslateFastValue = 0} 别的 {intervalTranslateFastValue -= 0.2}for(让intervalFast.children的孩子){child.style.transform = `translateX(${intervalTranslateFastValue}px)`}}功能 raqSlowAnimate(timeElapsed) {让 translateValue = -1 * ((timeElapsed/(1000/60) * 0.064) % 30)for(让 raq.children 的孩子){child.style.transform = `translateX(${translateValue}px)`}window.requestAnimationFrame(raqSlowAnimate)}功能 raqFastAnimate(timeElapsed) {让 translateValue = -1 * ((timeElapsed/(1000/60) * 0.2) % 30)for(让 raqFast.children 的孩子){child.style.transform = `translateX(${translateValue}px)`}window.requestAnimationFrame(raqFastAnimate)}window.setInterval(intervalSlowAnimation, 1000/60)window.setInterval(intervalFastAnimation, 1000/60)window.requestAnimationFrame(raqSlowAnimate)window.requestAnimationFrame(raqFastAnimate)})

* {box-sizing: 边框框;}#设置间隔,#setIntervalFast,#raq,#raqFast {位置:相对;宽度:100%;高度:50px;底边距:1em;溢出:隐藏;}

<input type="checkbox" id="background" label="Dark"><label>深色</label>

<span>在 [1000/60]ms 处使用 setInterval;每 50 毫秒平移 .2 像素(每 16 毫秒 .064 像素)</span><div id="setInterval"></div><span>使用 requestAnimationFrame;每 50 毫秒平移 .2 像素(每 16 毫秒 .064 像素)</span><div id="raq"></div><span>在 [1000/60]ms 处使用 setInterval;每 16 毫秒平移 .2 像素</span><div id="setIntervalFast"></div><span>使用 requestAnimationFrame;每 16 毫秒平移 .2 像素</span><div id="raqFast"></div>

使用 CSS 动画(更简单)

您还可以使用 CSS 动画轻松创建上面的动画:

window.onload = (() => {var background = document.querySelector('#background')var css = document.querySelector('#cssMethod')var cssFast = document.querySelector('#cssMethodFast')var backgroundColor, borderColorbackground.addEventListener('change', e => {backgroundColor = e.target.checked ?'#333333':'白色'css.style.backgroundColor = 背景颜色cssFast.style.backgroundColor = backgroundColor})让 cssDocFrag = document.createDocumentFragment()让 cssFastDocFrag = document.createDocumentFragment()让电流 = 0让步 = 30让 divNeeded = Math.ceil(css.getBoundingClientRect().width/30) + 1for (让 i = 0; i < divNeeded; i++) {for (让 j = 0; j <2; j++) {var element = document.createElement('div')element.style.position = '绝对'element.style.height = '50px'element.style.width = 步长 + 'px'element.style.left = 当前 + 'px'element.style['border-left'] = '1px 灰色实心'if (j == 0) css.appendChild(element)否则如果(j == 1)cssFast.appendChild(元素)}当前 += 步骤}css.appendChild(cssDocFrag)cssFast.appendChild(cssFastDocFrag)})

* {box-sizing: 边框框;}#css方法,#cssMethodFast {位置:相对;宽度:100%;高度:50px;底边距:1em;溢出:隐藏;}#cssMethod div {动画:6s 线性平移 0s 无限;}#cssMethodFast div {动画:2.4s 线性平移 0s 无限;}@关键帧翻译{来自 { 变换:translateX(-0px);}到 { 变换:translateX(-30px);}}

<input type="checkbox" id="background" label="Dark"><label>深色</label>

<span>使用CSS动画;每 50 毫秒平移 .2 像素</span><div id="cssMethod"></div><span>使用CSS动画;每 50 毫秒平移 0.625 像素</span><div id="cssMethodFast"></div>

I'm trying to display some sort of timeline and my goal is to get it smoothly refreshed.

I managed to get something better than absolute positionning using css transform property but I'm not very happy because there is some flickering (especially when the background is dark).

var background = document.querySelector('#background');
var position = document.querySelector('#position');
var transform = document.querySelector('#transform');

var backgroundColor, borderColor

background.addEventListener('change', e => {

	backgroundColor = e.target.checked ? '#333333' : 'white'
  position.style.backgroundColor = backgroundColor
  transform.style.backgroundColor = backgroundColor

});

let current = 0
let step = 30
for (let i = 0; i < 300; i++) {
	for (let j = 0; j < 2; j++) {
    var element = document.createElement('div')
    element.style.position = 'absolute'
    element.style.height = '50px'
    element.style.width = step + 'px'
    if(j) {
      element.style.left = current + 'px'
    } else {
      element.style.left = 0 + 'px'
      element.style.transform = 'translateX(' + current + 'px)'
    }
    element.style['border-left'] = '1px gray solid'
    if (j)
      position.appendChild(element)
    else
      transform.appendChild(element)
  }
  
  current += step
}

setInterval(refresh, 50);

let init = 0
function refresh() {
	init -= 0.2
	let current = init
  for (var i = 0; i < position.children.length; i++) {
    var c = position.children[i];
  	c.style.left = current + 'px'
  	current += step
  }
  current = init
  for (var i = 0; i < transform.children.length; i++) {
    var c = transform.children[i];
  	c.style.transform = 'translateX(' + current + 'px)'
  	current += step
  }
}

<html>
<body>
<div>
  <input type="checkbox" id="background" label="Dark"> 
  <label>Dark</label>
</div>
<span>Using css position<span>
<div id="position" style="width:100%;height:50px;margin-bottom:1em;"></div>

<span>Using css transform<span>
<div id="transform" style="width:100%;height:50px;"></div>
</body>
</html>

Is there a better way to do this?

解决方案

The reason the animation is flickering is that it's being translated very slowly at 20fps (1000ms/50). Aside from the low fps, using setInterval also does not guarantee that the callback function will be called every time (see here for an example). To make it smoother, simply add the refresh rate to 60 times per second, use requestAnimationFrame instead of setInterval to animate the objects, and increase the translation value per frame so that the animation is less choppy (imagine moving something by only .2px per 3 frames).

You're using a 300 divs to create the timeline animation and animating 300 divs at once. This can be a little more expensive. To streamline your animation, you can simply create enough amount of divs to fit the whole container plus one. Then, you simply need to translate until the leftmost div disappears before replaying the animation. It creates an illusion of a continuous animation when it actually isn't. Another more performant way is to only animate the container (i.e. wrapper) of the divs.

If you can alter either position or transform to create an animation, always opt for transform. Try reading the the link here in CSS Tricks and a very good explanation by Paul Irish here.

Furthermore, you shouldn't use setInterval to animate things; use requestAnimationFrame instead. There are, obviously, other methods too.

Using JS

Finally, here's a working example using both setInterval and requestAnimationFrame:

window.onload = (() => {
  var background = document.querySelector('#background')
  var interval = document.querySelector('#setInterval')
  var intervalFast = document.querySelector('#setIntervalFast')
  var raq = document.querySelector('#raq')
  var raqFast = document.querySelector('#raqFast')

  var backgroundColor, borderColor

  background.addEventListener('change', e => {
    backgroundColor = e.target.checked ? '#333333' : 'white'
    interval.style.backgroundColor = backgroundColor
    intervalFast.style.backgroundColor = backgroundColor
    raq.style.backgroundColor = backgroundColor
    raqFast.style.backgroundColor = backgroundColor
  })

  let intervalDocFrag = document.createDocumentFragment()
  let intervalFastDocFrag = document.createDocumentFragment()
  let raqDocFrag = document.createDocumentFragment()
  let raqFastDocFrag = document.createDocumentFragment()
  let current = 0
  let step = 30
  let divNeeded = Math.ceil(interval.getBoundingClientRect().width / 30) + 1 // Calculating how many divs are needed to fit one container + 1; 30 is the width of the div (29px + 1px of left border)
  for (let i = 0; i < divNeeded; i++) {
    for (let j = 0; j < 4; j++) {
      var element = document.createElement('div')
      element.style.position = 'absolute'
      element.style.height = '50px'
      element.style.width = step + 'px'

      element.style.left = current + 'px'

      element.style['border-left'] = '1px gray solid'
      if (j === 0)
        intervalDocFrag.appendChild(element)
      else if (j === 1)
        raqDocFrag.appendChild(element)
      else if (j === 2)
        intervalFastDocFrag.appendChild(element)
      else if (j === 3)
        raqFastDocFrag.appendChild(element)
    }

    current += step
  }

  interval.appendChild(intervalDocFrag)
  intervalFast.appendChild(intervalFastDocFrag)
  raq.appendChild(raqDocFrag)
  raqFast.appendChild(raqFastDocFrag)

  let intervalTranslateSlowValue = 0
  function intervalSlowAnimation() {
    if (Math.floor(intervalTranslateSlowValue) === -30) {
      intervalTranslateSlowValue = 0 // Resetting animation to create an endless timeline animating illusion
    } else {
      intervalTranslateSlowValue -= 0.064 // Gotten from 0.2 * 16 / 50
    }
    for (let child of interval.children) {
      child.style.transform = `translateX(${intervalTranslateSlowValue}px)`
    }
  }

  let intervalTranslateFastValue = 0
  function intervalFastAnimation() {
    if (Math.floor(intervalTranslateFastValue) === -30) {
      intervalTranslateFastValue = 0
    } else {
      intervalTranslateFastValue -= 0.2
    }
    for (let child of intervalFast.children) {
      child.style.transform = `translateX(${intervalTranslateFastValue}px)`
    }
  }

  function raqSlowAnimate(timeElapsed) {
    let translateValue = -1 * ((timeElapsed / (1000/60) * 0.064) % 30)
    for (let child of raq.children) {
      child.style.transform = `translateX(${translateValue}px)`
    }
    window.requestAnimationFrame(raqSlowAnimate)
  }

  function raqFastAnimate(timeElapsed) {
    let translateValue = -1 * ((timeElapsed / (1000/60) * 0.2) % 30)
    for (let child of raqFast.children) {
      child.style.transform = `translateX(${translateValue}px)`
    }
    window.requestAnimationFrame(raqFastAnimate)
  }

  window.setInterval(intervalSlowAnimation, 1000/60)
  window.setInterval(intervalFastAnimation, 1000/60)
  window.requestAnimationFrame(raqSlowAnimate)
  window.requestAnimationFrame(raqFastAnimate)
})

* {
  box-sizing: border-box;
}

#setInterval,
#setIntervalFast,
#raq,
#raqFast {
  position: relative;
  width: 100%;
  height: 50px;
  margin-bottom: 1em;
  overflow: hidden;
}

<div>
  <input type="checkbox" id="background" label="Dark">
  <label>Dark</label>
</div>

<span>Using setInterval at [1000/60]ms; translating .2px per 50ms (.064px per 16ms)</span>
<div id="setInterval"></div>

<span>Using requestAnimationFrame; translating .2px per 50ms (.064px per 16ms)</span>
<div id="raq"></div>

<span>Using setInterval at [1000/60]ms; translating .2px per 16ms</span>
<div id="setIntervalFast"></div>

<span>Using requestAnimationFrame; translating .2px per 16ms</span>
<div id="raqFast"></div>

Using CSS Animation (easier)

You can also use CSS animation to easily create the animation above:

window.onload = (() => {
  var background = document.querySelector('#background')
  var css = document.querySelector('#cssMethod')
  var cssFast = document.querySelector('#cssMethodFast')

  var backgroundColor, borderColor

  background.addEventListener('change', e => {
    backgroundColor = e.target.checked ? '#333333' : 'white'
    css.style.backgroundColor = backgroundColor
    cssFast.style.backgroundColor = backgroundColor
  })

  let cssDocFrag = document.createDocumentFragment()
  let cssFastDocFrag = document.createDocumentFragment()
  let current = 0
  let step = 30
  let divNeeded = Math.ceil(css.getBoundingClientRect().width / 30) + 1
  for (let i = 0; i < divNeeded; i++) {
    for (let j = 0; j < 2; j++) {
      var element = document.createElement('div')
      element.style.position = 'absolute'
      element.style.height = '50px'
      element.style.width = step + 'px'

      element.style.left = current + 'px'

      element.style['border-left'] = '1px gray solid'
      
      if (j == 0) css.appendChild(element)
      else if (j == 1) cssFast.appendChild(element)
    }

    current += step
  }

  css.appendChild(cssDocFrag)
  cssFast.appendChild(cssFastDocFrag)
})

* {
  box-sizing: border-box;
}

#cssMethod,
#cssMethodFast {
  position: relative;
  width: 100%;
  height: 50px;
  margin-bottom: 1em;
  overflow: hidden;
}

#cssMethod div {
  animation: 6s linear translation 0s infinite;
}

#cssMethodFast div {
  animation: 2.4s linear translation 0s infinite;
}

@keyframes translation {
  from { transform: translateX(-0px); }
  to { transform: translateX(-30px); }
}

<div>
  <input type="checkbox" id="background" label="Dark">
  <label>Dark</label>
</div>

<span>Using CSS animation; translating .2px per 50ms</span>
<div id="cssMethod"></div>

<span>Using CSS animation; translating .625px per 50ms</span>
<div id="cssMethodFast"></div>

这篇关于当位置不是整数时平滑过渡的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆