React Spring not respecting or firing animations on iOS?

64 Views Asked by At

I have a functional code on my desktop and in all browsers, where each button has a 4-second animation, and on load or hover, each button triggers the loading of different videos. However, on iOS, the animation or video doesn't play automatically. I have to manually touch or hover over a button to initiate the video or animation.

"use client"

import React, { useEffect, useRef, useState } from "react"

import Link from "next/link"

import { HomePageScreen as HomePageScreenType } from "@/types"
import { animated, SpringConfig, useSprings } from "react-spring"

import { IconsLibrary } from "@/components/shared"

const DURATION = 400
const INTERVAL = 4000

const easeOutQuad = (t: number) => t * (2 - t)

const easeInOutCubic = (t: number) =>
  t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1

type HomePageScreenProps = {
  data: HomePageScreenType[]
}

const HomePageScreen = ({ data }: HomePageScreenProps) => {
  if (!data) return null

  const [loaded, setLoaded] = useState({ videos: false, content: false })
  const [activeButton, setActiveButton] = useState<number | null>(null)

  useEffect(() => {
    const videoLoadListeners = data.map((item, index) => {
      const video = document.createElement("video")
      video.src = item.caseLink?.case.cloudinary.secure_url ?? ""
      video.preload = "auto"

      video.oncanplaythrough = () => {
        if (index === data.length - 1) {
          setLoaded((prev) => ({ ...prev, videos: true }))
        }
      }

      return video
    })

    const contentLoadTimeout = setTimeout(() => {
      setLoaded((prev) => ({ ...prev, content: true }))
    }, 0)

    return () => {
      videoLoadListeners.forEach((video) => {
        video.oncanplaythrough = null
        video.remove()
      })
      clearTimeout(contentLoadTimeout)
    }
  }, [data])

  useEffect(() => {
    if (loaded.videos && loaded.content) {
      setActiveButton(0)
      startInterval()
    }

    return () => clearInterval(intervalRef.current)
  }, [loaded.videos, loaded.content])

  const videoSprings = useSprings(
    data.length,
    data.map((item, index) => ({
      opacity: activeButton === index ? 1 : 0,
      scale: activeButton === index ? 1.1 : 1,
      config: {
        duration: DURATION,
        easing: activeButton === index ? easeInOutCubic : easeOutQuad
      } as SpringConfig
    }))
  )

  const intervalRef = useRef<NodeJS.Timeout | undefined>(undefined)

  const handleMouseEnter = (index: number) => {
    if (intervalRef.current !== null) {
      clearInterval(intervalRef.current)
    }

    setActiveButton(index)
  }

  const handleMouseLeave = () => {
    startInterval()
  }

  const startInterval = () => {
    if (intervalRef.current !== null) {
      clearInterval(intervalRef.current)
    }

    intervalRef.current = setInterval(() => {
      setActiveButton((prev) => (prev === null ? 0 : (prev + 1) % data.length))
    }, INTERVAL)
  }

  return (
    <section className="col-span-full overflow-hidden text-white bg-black absolute top-0 left-0 z-0 w-full h-full px-5 md:px-10 flex flex-col items-center justify-between">
      <div className="absolute bottom-36 left-[30px] z-[400] flex w-full flex-wrap gap-[15px] md:bottom-20 max-w-[90%] md:left-[40px] md:max-w-[90%] xl:left-[150px] lg:max-w-[920px]">
        <h1 className="hero z-[10] font-micro-medium text-[38px] leading-tight text-white md:text-headingL inline-block">
          {data.map((item, index) => (
            <span key={item.caseLink?.case.slug.current}>
              <span className="mr-2 md:mr-4">{item.textBlock}</span>
              <div className="inline-flex">
                <Link
                  className="emoji eyes text-inherit text-lg md:text-2xl -top-2 mr-2 md:mr-4 overflow-hidden px-[1.25rem] py-[0.297rem] md:py-[0.625rem] border-2 border-white rounded-[100px] relative transition-all duration-300 ease-in-out hover:bg-white hover:text-black group"
                  href={`/cases/${item.caseLink?.case.slug.current}`}
                  onMouseEnter={() => handleMouseEnter(index)}
                  onMouseLeave={handleMouseLeave}
                >
                  <span
                    className={`mix-blend-difference z-[100] relative group-hover:mix-blend-normal text-current`}
                  >
                    {item.caseLink?.case.title}
                  </span>

                  <span
                    className={`h-full absolute z-10 top-0 left-0 transition-all duration-500 bg-white  ${
                      activeButton === index ? "animate-progress" : "opacity-0 w-0"
                    }`}
                  ></span>
                </Link>
              </div>
            </span>
          ))}
          <Link
            href="/cases"
            className="group emoji eyes inline-flex -top-2 h-[36px] md:h-[54px] sm:-ml-1 place-content-center items-center w-[70px] md:w-[90px] overflow-hidden px-[1.25rem] py-[0.625rem] border-2 border-white/50 rounded-[100px] relative transition-all duration-300 ease-in-out hover:bg-white"
          >
            <IconsLibrary
              type="arrow-right"
              className="h-[12px] w-[14px] md:h-[16px] md:w-[18px] fill-white group-hover:fill-black transition-all duration-300 ease-in-out"
            />
          </Link>
        </h1>
      </div>

      {data.map((item, index) => {
        const originalUrl = item.caseLink?.case.cloudinary.secure_url

        return (
          <animated.video
            src={originalUrl}
            autoPlay
            loop
            key={index}
            muted
            playsInline
            className="absolute w-screen h-screen top-0 left-0 object-cover"
            style={{
              ...videoSprings[index],
              transformOrigin: "center center",
              transform: videoSprings[index].scale?.to((s) => `scale(${s})`),
              opacity: videoSprings[index].opacity?.to((o) => o.toFixed(2))
            }}
          />
        )
      })}

      <div className="fixed top-0 left-0 w-screen h-screen bg-black/40 z-10"></div>
    </section>
  )
}

export default HomePageScreen
1

There are 1 best solutions below

0
On

Figured it out. Thanks low power mode and removing video.oncanplaythrough = () => solved the problem.