Huxzhi

Huxzhi

不是工具难用,不是方法难学,而是不知道去向何方

视差 Banner 实现二次函数的跟随移动轨迹

#UI设计

学到的知识点

  • 使用js 设置偏移量的方式来 requestAnimationFrame ,近似实现二次函数的逼近效果,
  • 设置translate,不改变DOM结构,降低性能损耗

需要考虑设备的帧率,需要引入时间

视差 Banner 实现二次函数的跟随移动轨迹

🔗 本期用到的代码和工具链接看这里(复制到浏览器打开): CodePen https://codepen.io/editor/linxiang-webcraft/pen/019d53ed-f01c-7938-8c51-bb36a15a08aa

const container = document.querySelector('#parallax-container')

const layers = [
  {
    element: document.querySelector('.layer-title'),
    offsetX: 12,
    offsetY: 4,
  },
  {
    element: document.querySelector('.layer-planet-fg'),
    offsetX: 24,
    offsetY: 8,
  },
  {
    element: document.querySelector('.layer-asteroid'),
    offsetX: 48,
    offsetY: 16,
  },
  {
    element: document.querySelector('.layer-stars'),
    offsetX: -3,
    offsetY: -1,
  },
  {
    element: document.querySelector('.layer-planet-bg'),
    offsetX: -9,
    offsetY: -3,
  },
]

let targetX = 0
let targetY = 0
container.addEventListener('pointermove', (e) => {
  const rect = container.getBoundingClientRect()
  const localX = e.clientX - rect.left
  const localY = e.clientY - rect.top

  const centerX = localX - rect.width / 2
  const centerY = localY - rect.height / 2

  targetX = centerX / (rect.width / 2)
  targetY = centerY / (rect.height / 2)
  startRender()
})

let scheduledFrame = false
function startRender() {
  if (scheduledFrame) {
    return
  }
  requestAnimationFrame(render)
  scheduledFrame = true
}

let currentX = 0
let currentY = 0
const lerpFactor = 0.1
const EPSILON = 0.001
function render() {
  currentX += (targetX - currentX) * lerpFactor
  currentY += (targetY - currentY) * lerpFactor

  if (
    Math.abs(targetX - currentX) < EPSILON &&
    Math.abs(targetY - currentY) < EPSILON
  ) {
    currentX = targetX
    currentY = targetY
    scheduledFrame = false
    console.log('animation stopped!')
  }

  for (const layer of layers) {
    const moveX = currentX * layer.offsetX
    const moveY = currentY * layer.offsetY

    layer.element.style.transform = `translate(${moveX}px, ${moveY}px)`
  }

  if (scheduledFrame) {
    requestAnimationFrame(render)
  }
}

container.addEventListener('pointerleave', () => {
  targetX = 0
  targetY = 0
  startRender()
})