Причина, по которой анимация мерцает, заключается в том, что она очень медленно переводится со скоростью 20 кадров в секунду (1000ms/50
). Помимо низкого fps, использование setInterval
также не гарантирует, что функция обратного вызова будет вызываться каждый раз (см. здесь для примера ). Чтобы сделать его более плавным, просто добавьте скорость refre sh к 60 разам в секунду, используйте requestAnimationFrame
вместо setInterval
для анимации объектов и увеличьте значение перевода для кадра, чтобы анимация была менее прерывистой (представьте, что вы перемещаетесь только на .2px за 3 кадра).
Вы используете 300 делений для создания анимации на временной шкале и анимации одновременно 300 делений. Это может быть немного дороже. Чтобы упростить анимацию, вы можете просто создать достаточное количество элементов div, чтобы вместить весь контейнер плюс один. Затем вам просто нужно перевести, пока самый левый div не исчезнет перед воспроизведением анимации. Это создает иллюзию непрерывной анимации, когда на самом деле это не так. Еще один более производительный способ состоит в том, чтобы анимировать только контейнер (т.е. оболочку) div.
Если вы можете изменить position
или transform
для создания анимации, всегда выбирайте transform
. Попробуйте прочитать ссылку здесь в CSS Трюки и очень хорошее объяснение Пол Ири sh здесь.
Кроме того, вы не должны использовать setInterval
оживить вещи; используйте вместо этого requestAnimationFrame
. Очевидно, есть и другие методы.
Использование JS
Наконец, вот рабочий пример использования setInterval
и 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>
Использование CSS Анимация (проще)
Вы также можете использовать CSS анимацию, чтобы легко создать анимацию выше:
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>