Introduction:

Animation is an important part of the front end, and fun interactive animation implementations can often surprise and excite users and make them more willing to use your product. Mastering animation is one of the necessary skills for front-end engineers. This article will cover how to implement a volume emission animation from scratch.

To the chase

A few days ago, a friend shared a link in wechat moments — what is the most anti-human design you have ever seen? . There are a lot of anti-human [volume control] designs listed here, if you are interested, you can click on them. 👀

What attracts me is the following one, [Volume Catapult]. Hold down the volume icon, lift the barrel, release the mouse to launch and set the volume, which is really interesting.

Anxious friends can see the final result first.

decomposition

This animation can be roughly divided into two parts to do, 🔊 volume icon and pinball Bar, programming can be divided into two independent modules to do.

<VolumnShotter /> -state: -volumn // volume value -increacing // if any <Shotter /> -props: - volumn - onMouseDown/onMouseUp <BallBar /> - props: - increacing - methods: - play(volumn: number)Copy the code

The component controls rotation angles and bubbleSize based on the props. Volumn value, while responding to mouse events. The
component controls whether the ball is displayed or not by props. Increacing and provides the play method to call. The two components are mapped to different states according to volumn values.

<Shotter />

To do this, use an SVG volume icon. Go to FlatIcon and find the appropriate icon to download.

When pressed, there will be a small scale effect inside the volume, using the mask effect. You can drag it inside Sketch and use the mask feature to do this.

Click group to export SVG and get a code as follows:


      
<svg width="236px" height="221px" viewBox="0 0 236 221" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <title>marshalling</title>
    <defs>
        <path d="M137.534889, 0.782686392 C134.789556..." id="path-1"></path>
    </defs>
    <g id="Page 1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g>
            <mask id="mask-2" fill="white">
                <use xlink:href="#path-1"></use>
            </mask>
            <use fill="#D8D8D8" xlink:href="#path-1"></use>
            <circle fill="#F7B500" mask="url(#mask-2)" cx="0" cy="110" r="71"></circle>
        </g>
    </g>
</svg>
Copy the code

See that the resulting exported code uses the SVG mask tag for the mask effect. From CanIUse, this feature supports 75.17% of browsers.

Import a copy of the SVG code into the React component and use the props. Volumn value to dynamically change the rotation Angle and the radius of the circle element.

import React from "react";
import { mapping } from "./math";

export default (props: Props) => {
  const { volumn = 0. rest } = props;const circleRadius = mapping(volumn, 0.100.0.240);
  const transform = `rotate(${mapping(volumn, 0.100.0, -45)}deg)`;

  return (
    <svg
      // .
      style={{
        transform
      }}
      {. rest}
    >
      // ...
      <circle
        r={circleRadius}
      />
    </svg>
  );
};
Copy the code

Here a simple value mapping is used to map volumn of 0~100 to circle radius 0~240 and rotation Angle 0~-45deg. The method of value mapping is as follows:

export const mapping = (num, inMin, inMax, outMin, outMax) = > {
  return ((num - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
};
Copy the code

And then dynamically modify the volumn and that’s the end of the animation.

<BallBar />

This component needs to expose a play method. How do we provide a play method when writing React Hook? The answer is useImperativeHandle + forwardRef.

export default React.forwardRef(({ increacing }: Props, ref) = > {
  useImperativeHandle(ref, () = > ({
    play: volumn= > {
      console.log(volumn)
    }
  }));

  return (
    <div className="ball-bar">/ /...</div>
  );
});
Copy the code


and call ballbarref.current. Play (column) from ref.

Then enter the part to realize the ball animation, the ball starting state including the starting height, starting speed (vector), end position and other information are determined by volumn, can be calculated in the play method.

You can calculate the data you need by using vector factorization and trigonometric basics. If you are not familiar with trigonometry, you can read another article I wrote earlier about the application of trigonometry in front end animation

const radians = angleToRadians(mapping(volumn, 0.100.0, -45));
const startY = Math.tan(radians) * 54;
const startX = 0;

// The higher the volume, the higher the initial speed. Ignore the magic number here
const initalVelocity = mapping(volumn, 0.100.0.999);
const vx = initalVelocity * Math.cos(radians);
const vy = initalVelocity * Math.sin(radians)
const endX = mapping(volumn, 0.100.0, barRef.current.clientWidth);
const endY = 0;
Copy the code

We have the starting state, we have the ending position. The ball animation can be implemented in two ways. One is to dynamically generate stiffness based on the SVG Path and then move the ball in a certain Easing Function curve. The second is a library based on physics simulation. This Demo uses ReactSping.

useSpring({
    from: { y: startY }, // Start position
    to: { y: endY }, // Target position
    reset: resetRef.current, // Reset every time you play
    config: {
      velocity: vy, // Start speed
      friction: 10.// Friction, ignore magic number
      tension: 60.// Pull, ignore magic number
      clamp: true // Stop immediately when out of range}});Copy the code

Then apply the useSpring value to animated. Div.

{increacing ? null : (
    <animated.div
      className="ball"
      style={{
        transform: y.interpolate(
          value= > `translateY(${value}px)`
        )
      }}
    />
 )}
Copy the code

ReactSpring is also easy to use, and more configuration items can be found in the documentation.

<VolumnShotter />

Then string the two components together. Listen for Shotter’s mouse event and start requestAnimationFrame when pressed, with each tick changing the volumn value.

useEffect(() = > {
    function tick() {
      setVolumn(v= > clamp(v + speed, 0.100));
      reqRef.current = requestAnimationFrame(() = > { tick() });
    }

    if (increacing) {
      tick();
    } else {
      setVolumn(0);
    }

    return () = > {
      cancelAnimationFrame(reqRef.current);
    };
  }, [increacing, speed]);
Copy the code

During mouseUp, the play method is called to pass the volumn.

const toggle = () = > {
    if (increacing && barRef.current) {
        (barRef.current as any)!.play(volumn);
    }
};
Copy the code

Put all of the above together and you can make a fun volume ejection animation. The full code can be seen on CodeSandBox.

conclusion

For quick implementation, some Magic numbers are hardcoded in this example, which is a bad demonstration. Scalability and maintainability should be considered in real projects.

It also needs to be tuned to make the display of Volumn more accurate

Windfall

As a personal hobby, I usually collect some good-looking pictures and interesting animations, etc. When I have time, I will try to achieve some effects/animations. In January of ’16, I wasDribbbleI saw an animation that painted the screen, thought it was fun, tried to implement it using my new knowledge at the time, and then posted toCodepenOn. Later in the internship, the interviewer happened to see my implementation, so I successfully won the internship offer.

Interested friends can pay attention to my nuggets, I will plan to write more animation entry level articles.