Home > Net >  Add timer so that image changes automatically in 3 seconds
Add timer so that image changes automatically in 3 seconds

Time:02-24

Hey guys I wanted to add timer along with these gestures to the following react-spring example. I've been struck with this since a long time. It'll be of great assistance. Also I'm working with makestyles from material ui. It would be an additional help if you can tell me how to convert the css in to make styles too. Link to the code: https://codesandbox.io/embed/j0y0vpz59

import { render } from "react-dom";
import React, { useState, useEffect } from "react";
import { useSprings, animated, interpolate } from "react-spring";
import { useGesture } from "react-use-gesture";
import img1 from "./assets/slideImg1.jpg";
import img2 from "./assets/slideImg2.gif";
import img3 from "./assets/slideImg3.gif";
import img4 from "./assets/slideImg4.jpg";
import "./index.css";

const cards = [
  img1,
  img2,
  img3,
  img4,
  "https://upload.wikimedia.org/wikipedia/en/thumb/8/88/RWS_Tarot_02_High_Priestess.jpg/690px-RWS_Tarot_02_High_Priestess.jpg",
  "https://upload.wikimedia.org/wikipedia/en/d/de/RWS_Tarot_01_Magician.jpg",
];

// These two are just helpers, they curate spring data, values that are later being interpolated into css
const to = (i) => ({
  x: 0,
  y: i * -4,
  scale: 1,
  rot: -10   Math.random() * 20,
  delay: i * 100,
});
const from = (i) => ({ x: 0, rot: 0, scale: 1.5, y: -1000 });
// This is being used down there in the view, it interpolates rotation and scale into a css transform
const trans = (r, s) =>
  `perspective(1500px) rotateX(30deg) rotateY(${
    r / 10
  }deg) rotateZ(${r}deg) scale(${s})`;

function Deck() {
  const [gone] = useState(() => new Set()); // The set flags all the cards that are flicked out
  const [fly, setFly] = useState(false);
  const [props, set] = useSprings(cards.length, (i) => ({
    ...to(i),
    from: from(i),
  })); // Create a bunch of springs using the helpers above
  // Create a gesture, we're interested in down-state, delta (current-pos - click-pos), direction and velocity
  const bind = useGesture(
    ({
      args: [index],
      down,
      delta: [xDelta],
      distance,
      direction: [xDir],
      velocity,
    }) => {
      const trigger = velocity > 0.2; // If you flick hard enough it should trigger the card to fly out
      const dir = xDir < 0 ? -1 : 1; // Direction should either point left or right
      if (!down && trigger) gone.add(index); // If button/finger's up and trigger velocity is reached, we flag the card ready to fly out
      set((i) => {
        if (index !== i) return; // We're only interested in changing spring-data for the current spring
        const isGone = gone.has(index);
        const x = isGone ? (200   window.innerWidth) * dir : down ? xDelta : 0; // When a card is gone it flys out left or right, otherwise goes back to zero
        const rot = xDelta / 100   (isGone ? dir * 10 * velocity : 0); // How much the card tilts, flicking it harder makes it rotate faster
        const scale = down ? 1.1 : 1; // Active cards lift up a bit
        return {
          x,
          rot,
          scale,
          delay: undefined,
          config: { friction: 50, tension: down ? 800 : isGone ? 200 : 500 },
        };
      });
      if (!down && gone.size === cards.length)
        setTimeout(() => gone.clear() || set((i) => to(i)), 600);
    }
  );
  useEffect(() => {
    setFly(false);
    const timerId = setInterval(() => {
      setFly(true);
    }, 1000);
    return () => clearInterval(timerId);
  }, [fly]);
  // Now we're just mapping the animated values to our view, that's it. Btw, this component only renders once. :-)
  return props.map(({ x, y, rot, scale }, i) => (
    <animated.div
      key={i}
      style={{
        transform: interpolate(
          [x, y],
          (x, y) => `translate3d(${x}px,${y}px,0)`
        ),
      }}
    >
      {/* This is the card itself, we're binding our gesture to it (and inject its index so we know which is which) */}
      <animated.div
        {...bind(i)}
        style={{
          transform: interpolate([rot, scale], trans),
          backgroundImage: `url(${cards[i]})`,
        }}
      />
    </animated.div>
  ));
}

render(<Deck />, document.getElementById("root"));

//css
* {
  box-sizing: border-box;
}

html,
body {
  overscroll-behavior-y: contain;
  margin: 0;
  padding: 0;
  height: 100%;
  width: 100%;
  user-select: none;
  font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir,
    helvetica neue, helvetica, ubuntu, roboto, noto, segoe ui, arial, sans-serif;
  position: fixed;
  overflow: hidden;
}

#root {
  background: lightblue;
  position: fixed;
  overflow: hidden;
  width: 100%;
  height: 100%;
  cursor: url("https://uploads.codesandbox.io/uploads/user/b3e56831-8b98-4fee-b941-0e27f39883ab/Ad1_-cursor.png")
      39 39,
    auto;
}

#root > div {
  position: absolute;
  width: 100vw;
  height: 100vh;
  will-change: transform;
  display: flex;
  align-items: center;
  justify-content: center;
}

#root > div > div {
  background-color: white;
  background-size: auto 85%;
  background-repeat: no-repeat;
  background-position: center center;
  width: 45vh;
  max-width: 300px;
  height: 85vh;
  max-height: 570px;
  will-change: transform;
  border-radius: 10px;
  box-shadow: 0 12.5px 100px -10px rgba(50, 50, 73, 0.4),
    0 10px 10px -10px rgba(50, 50, 73, 0.3);
}


```[enter image description here][1]


  [1]: https://i.stack.imgur.com/D77CT.png

CodePudding user response:

This is a great question!

The solution is to add a useEffect hook that calls a setInterval timer to update the springs.

First, the code sandbox: https://codesandbox.io/s/epic-mendeleev-w8zm7s?file=/src/index.js

And here is the useEffect hook. Notice that the callback returns a cleanup function that clears the intervals and timers.

useEffect(() => {
    const timers = [];
    const interval = setInterval(() => {
      const index = cards.length - gone.size - 1;
      gone.add(index);

      set((i) => {
        if (index !== i) return;
        const dir = i % 2 === 0 ? -1 : 1;
        const velocity = 1;
        return {
          x: (200   window.innerWidth) * dir,
          rot: 0 / 100   dir * 10 * velocity,
          scale: 1.1,
          delay: undefined,
          config: { friction: 50, tension: 200 },
        };
      });

      if (gone.size === cards.length) {
        const timer = setTimeout(() => {
          gone.clear();
          set((i) => to(i));
        }, 1000);
        timers.push(timer);
      }
    }, 3000);
    return () => {
      clearInterval(interval);
      timers.forEach((timer) => clearTimeout(timer));
    };
  }, []);

When the interval callback is executed, first we determine which card to swipe off the screen. Then we add that to the gone Set that is also shared with the useGesture hook. This is important, because if the user swipes a card off the screen, we want to move forward and swipe the next card.

      const index = cards.length - gone.size - 1;
      gone.add(index);

Then we call the set callback given to us from useSprings. This is what does the animation. I copied most of the code from the set callback in the useGesture.

      set((i) => {
        if (index !== i) return;
        const dir = i % 2 === 0 ? -1 : 1;
        const velocity = 1;
        return {
          x: (200   window.innerWidth) * dir,
          rot: 0 / 100   dir * 10 * velocity,
          scale: 1.1,
          delay: undefined,
          config: { friction: 50, tension: 200 },
        };
      });

And finally, if we've reached the end of the list, we set a 1 second timer, and clear the list, starting over.

      if (gone.size === cards.length) {
        const timer = setTimeout(() => {
          gone.clear();
          set((i) => to(i));
        }, 1000);
        timers.push(timer);
      }
  • Related