🐩

要素をフワフワさせたい

2023/07/10に公開

世の中には「要素をフワフワさせたい」という需要が一定数あります。要素を上下に動かすシンプルなアニメーションですが、いざ実装してみると自然にフワフワさせるのは案外難しい...と思われるかもしれません。

そんなあなたにプテラノドン!

上下にフワフワ

上下に動くキーフレームを作って、in-out 系のイージングを指定して alternate-reverse したらフワフワします。

.target {
  animation: floating-y 1.8s ease-in-out infinite alternate-reverse;
}
@keyframes floating-y {
  0% {
    transform: translateY(-10%);
  }
  100% {
    transform: translateY(10%);
  }
}

左右にもフワフワ

上下の動きだけだと単調な場合は、少し左右の動きを足したら良くなるかもしれません。div で囲ってx方向に動かします。

.wrapper {
  animation: floating-x 7.2s ease-in-out infinite alternate-reverse;
}
.target {
  animation: floating-y 1.8s ease-in-out infinite alternate-reverse;
}
@keyframes floating-x {
  0% {
    transform: translateX(-5%);
  }
  100% {
    transform: translateX(5%);
  }
}
@keyframes floating-y {
  0% {
    transform: translateY(-10%);
  }
  100% {
    transform: translateY(10%);
  }
}

正弦波でフワフワ

さらに複雑なフワフワを実装したい場合、闇雲にHTMLのネストを深くするのは罪悪感があるので、JSを検討するタイミングかもしれません。JSでフワフワさせたいときは、正弦波に乗せてあげるとシンプルに実装できます。このサンプルコードでは若干の rotate を追加しました。

style を毎フレーム更新していますが、 2Dの canvas でもフワフワの考え方は同じです。

TypeScript
const fly = (target: HTMLElement) => {
  const startTime = performance.now()
  const amplitude = { x: 10, y: 10, rotation: -2 }
  const speed = { x: 0.0004, y: 0.001 }

  const tick = () => {
    const diff = performance.now() - startTime
    const x = amplitude.x * Math.sin(speed.x * diff)
    const y = amplitude.y * Math.sin(speed.y * diff)
    const rotation = amplitude.rotation * (1 + Math.sin(speed.y * diff))

    target.style.transform = `rotate(${rotation}deg) translate(${x}%, ${y}%)`

    requestAnimationFrame(tick)
  }

  tick()
}

const target = document.querySelector<HTMLElement>('.target')

if (target) {
  fly(target)
}
JavaScript
const fly = (target) => {
  const startTime = performance.now()
  const amplitude = { x: 10, y: 10, rotation: -2 }
  const speed = { x: 0.0004, y: 0.001 }

  const tick = () => {
    const diff = performance.now() - startTime
    const x = amplitude.x * Math.sin(speed.x * diff)
    const y = amplitude.y * Math.sin(speed.y * diff)
    const rotation = amplitude.rotation * (1 + Math.sin(speed.y * diff))

    target.style.transform = `rotate(${rotation}deg) translate(${x}%, ${y}%)`

    requestAnimationFrame(tick)
  }

  tick()
}

const target = document.querySelector('.target')

if (target) {
  fly(target)
}

インタラクティブにフワフワ

フワフワだけでは飽き足らず、画面いっぱいに動かしたい、かつクリックで加速させたいという要件が追加されたら...パワーで解決!横100%の動きはキーフレームの方が楽なので Web Animations API を使いました。

これであなたもプテラノドン!

TypeScript
const fly = (wrapper: HTMLElement, target: HTMLElement) => {
  const amplitude = { y: 14, rotation: -2.5 }
  const speed = { y: 0.001 }
  const anime = wrapper.animate(
    [{ transform: 'translateX(100%)' }, { transform: 'translateX(-100vw)' }],
    { duration: window.innerWidth * 24, iterations: Infinity}
  )

  let multiplier = 1
  let previousTime = performance.now()
  let yRadian = 0

  const tick = () => {
    const now = performance.now()
    const diff = now - previousTime

    previousTime = now
    yRadian += multiplier * speed.y * diff

    const y = amplitude.y * Math.sin(yRadian)
    const rotation = amplitude.rotation * (1 + Math.sin(yRadian))

    target.style.transform = `rotate(${rotation}deg) translateY(${y}%)`

    requestAnimationFrame(tick)
  }

  tick()

  return {
    faster: () => {
      multiplier += 1
      anime.updatePlaybackRate(multiplier)
    }
  }
}

const wrapper = document.querySelector<HTMLElement>('.wrapper')
const target = document.querySelector<HTMLElement>('.target')

if (wrapper && target) {
  const flyingPteranodon = fly(wrapper, target)

  document.body.addEventListener('click', () => flyingPteranodon.faster())
}
JavaScript
const fly = (wrapper, target) => {
  const amplitude = { y: 14, rotation: -2.5 }
  const speed = { y: 0.001 }
  const anime = wrapper.animate(
    [{ transform: 'translateX(100%)' }, { transform: 'translateX(-100vw)' }],
    { duration: window.innerWidth * 24, iterations: Infinity}
  )

  let multiplier = 1
  let previousTime = performance.now()
  let yRadian = 0

  const tick = () => {
    const now = performance.now()
    const diff = now - previousTime

    previousTime = now
    yRadian += multiplier * speed.y * diff

    const y = amplitude.y * Math.sin(yRadian)
    const rotation = amplitude.rotation * (1 + Math.sin(yRadian))

    target.style.transform = `rotate(${rotation}deg) translateY(${y}%)`

    requestAnimationFrame(tick)
  }

  tick()

  return {
    faster: () => {
      multiplier += 1
      anime.updatePlaybackRate(multiplier)
    }
  }
}

const wrapper = document.querySelector('.wrapper')
const target = document.querySelector('.target')

if (wrapper && target) {
  const flyingPteranodon = fly(wrapper, target)

  document.body.addEventListener('click', () => flyingPteranodon.faster())
}

▼ 素材
プテラノドン - iStockで購入
宇宙(NASA) - Unsplash

▼ こちらの商品もおすすめ
プテラノ丼

Discussion