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
<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.