vuejs23dcss-animationscss-transformsperspective

why my 3D animation runs smoothly on desktop but not on mobile?


it runs great on desktop but so janky on mobile :( sorry can't show you the result bcs I can't get permission TT_TT basically the animation is about having objects in 3D context of one perspective flying through z-index to your direction (and gets blurry when nearer)

I suspect it might have something to do with moving thousands of pixels? :( How can I improve performance for mobile? Thanks!

HTML: `

<template>
  <div :class="{'js-loading': !hasLoaded}">
    <img class="circle-bg" src="../assets/scene2-coins/circle-big-bg.svg" alt="circle-bg" @load="onImgLoad">
    <img class="circle-bg--2" src="../assets/scene2-coins/circle-big-bg.svg" alt="circle-bg--2" @load="onImgLoad">
    <img class="coin-silver" src="../assets/scene2-coins/coin-silver.svg" alt="coin-silver" @load="onImgLoad">
    <img class="stars" src="../assets/scene2-coins/stars.svg" alt="stars" @load="onImgLoad">
    <img class="confetti" src="../assets/scene2-coins/confetti.svg" alt="confetti" @load="onImgLoad">
    <img class="confetti--2" src="../assets/scene2-coins/confetti.svg" alt="confetti--2" @load="onImgLoad">
    <img class="coin-platinum" src="../assets/scene2-coins/coin-platinum.svg" alt="coin-platinum" @load="onImgLoad">
    <img class="coin-gold" src="../assets/scene2-coins/coin-gold.svg" alt="coin-gold" @load="onImgLoad">
    <img class="coin-diamond" src="../assets/scene2-coins/coin-diamond.svg" alt="coin-diamond" @load="onImgLoad">
  </div>
</template>

`

SCSS: `

<style lang="scss" scoped>
  .js-loading *,
  .js-loading *:before,
  .js-loading *:after {
    animation-play-state: paused !important;
  }

  div {
    display: flex;
    justify-content: center;
    align-items: center;

    perspective: 500px;
    perspective-origin: 50% 50%;

    img { 
      position: inherit;
      display: block;
    }

    ///////////////////////////// BACKGROUND

    .circle-bg {
      min-width: 1200px;
      min-height: 1200px;
      // animation: circle-bg 1000ms linear both 2000ms;
      animation-name: circle-bg;
      animation-duration: 1000ms;
      animation-timing-function: linear;
      animation-delay: 2000ms;
      animation-iteration-count: 1;
      animation-direction: normal;
      animation-fill-mode: both;
    }

    @keyframes circle-bg  {
      0% { transform: rotate(180deg) scale3D(.65); }
      25% { transform: rotate(180deg) scale3D(.7375); }
      50% { transform: rotate(180deg) scale3D(.825); }
      75% { transform: rotate(180deg) scale3D(.9125); }
      100% { transform: rotate(180deg) scale3D(1); }
    }

    .circle-bg--2 {
      min-width: 1500px;
      min-height: 1500px;
      // animation: circle-bg--2 1500ms linear both 2000ms;
      animation-name: circle-bg--2;
      animation-duration: 1500ms;
      animation-timing-function: linear;
      animation-delay: 2000ms;
      animation-iteration-count: 1;
      animation-direction: normal;
      animation-fill-mode: both;
    }

    @keyframes circle-bg--2  {
      0% { transform: translate3D(0, 0, -3000px); }
      25% { transform: translate3D(0, 0, -2250px); }
      50% { transform: translate3D(0, 0, -1500px); }
      75% { transform: translate3D(0, 0, -750px); }
      100% { transform: translate3D(0, 0, 0px); }
    }

    ///////////////////////////// STARS & CONFETTI

    .stars { 
      // animation: stars 1500ms linear both 2000ms;
      animation-name: stars;
      animation-duration: 1500ms;
      animation-timing-function: linear;
      animation-delay: 2000ms;
      animation-iteration-count: 1;
      animation-direction: normal;
      animation-fill-mode: both;
    }

    @keyframes stars {
      0% { transform: translateZ(-500px); }
      100% { transform: translateZ(500px); }
    }

    .confetti {
      width: 50px;
      height: 50px;
      top: 120px;
      left: 250px;
      // animation: confetti 1500ms linear both 2000ms;
      animation-name: confetti;
      animation-duration: 1500ms;
      animation-timing-function: linear;
      animation-delay: 2000ms;
      animation-iteration-count: 1;
      animation-direction: normal;
      animation-fill-mode: both;
    }

    @keyframes confetti {
      0% { transform: translateZ(-500px); filter: blur(0px); }
      100% { transform: translateZ(500px); filter: blur(2px); }
    }

    .confetti--2 {
      width: 100px;
      height: 100px;
      top: 480px;
      left: 20px;
      // animation: confetti--2 1500ms linear both 2000ms;
      animation-name: confetti--2;
      animation-duration: 1500ms;
      animation-timing-function: linear;
      animation-delay: 2000ms;
      animation-iteration-count: 1;
      animation-direction: normal;
      animation-fill-mode: both;
    }

    @keyframes confetti--2 {
      0% { transform: skew(-20deg, 10deg) translateZ(-500px); filter: blur(0px); }
      100% { transform: skew(-20deg, 10deg) translateZ(500px); filter: blur(2px); }
    }

    ///////////////////////////// COINS
    
    @mixin all-coins {
      width: 100px;
      height: 100px;
    }

    .coin-diamond {
      @include all-coins;
      top: 330px;
      left: 180px;
      // animation: coin-diamond 1500ms linear both 2000ms;
      animation-name: coin-diamond;
      animation-duration: 1500ms;
      animation-timing-function: linear;
      animation-delay: 2000ms;
      animation-iteration-count: 1;
      animation-direction: normal;
      animation-fill-mode: both;
    }

    @keyframes coin-diamond {
      0% { transform: rotate(-45deg) translateZ(100px); filter: blur(0px); }
      100% { transform: rotate(20deg) translateZ(1100px); filter: blur(5px); }
    }

    .coin-gold {
      @include all-coins;
      top: 300px;
      left: 50px;
      // animation: coin-gold 1500ms linear both 2000ms;
      animation-name: coin-gold;
      animation-duration: 1500ms;
      animation-timing-function: linear;
      animation-delay: 2000ms;
      animation-iteration-count: 1;
      animation-direction: normal;
      animation-fill-mode: both;
    }

    @keyframes coin-gold {
      0% { transform: rotate(20deg) translateZ(-100px); filter: blur(0px); }
      100% { transform: rotate(-40deg) translateZ(900px); filter: blur(4px); }
    }

    .coin-platinum {
      @include all-coins;
      top: 220px;
      left: 200px;
      // animation: coin-platinum 1500ms linear both 2000ms;
      animation-name: coin-platinum;
      animation-duration: 1500ms;
      animation-timing-function: linear;
      animation-delay: 2000ms;
      animation-iteration-count: 1;
      animation-direction: normal;
      animation-fill-mode: both;
    }

    @keyframes coin-platinum {
      0% { transform: rotate(20deg) translateZ(-300px); filter: blur(0px); }
      100% { transform: rotate(60deg) translateZ(700px); filter: blur(3px); }
    }

    .coin-silver {
      @include all-coins;
      top: 180px;
      left: 50px;
      // animation: coin-silver 1500ms linear both 2000ms;
      animation-name: coin-silver;
      animation-duration: 1500ms;
      animation-timing-function: linear;
      animation-delay: 2000ms;
      animation-iteration-count: 1;
      animation-direction: normal;
      animation-fill-mode: both;
    }

    @keyframes coin-silver {
      0% { transform: rotate(0deg) translateZ(-500px); filter: blur(0px); }
      100% { transform: rotate(60deg) translateZ(500px); filter: blur(2px); }
    }
  }
</style>

`

I've tried to optimize by adding several trick code:

  1. display block on all
  2. not using shorthand animation
  3. using class animation paused until img @load is true
  4. have tried to use will-change, but later deleted it since somebody said it's better to be used in JS - now using translate3D and scale3D (on some, haven't changed the rest of the code)
  5. using more than 2 keyframes (on some, haven't changed the rest of the code)

btw all assets are compressed under 100kb


Solution

  • I suspect is the blur animation, filter blur is a GPU destroyer when you abuse of that. Hope it helps!