原版效果是在dribbble上看到的,作者是Steven Liu
这是原版GIF:
最终实现效果视频可以看我Dou音,DY号:G_console
这是个平面模拟3D的效果,对于单个小球来说,只有位移、缩放、颜色变化。所以只用CSS就可以实现。
以第一个小球动画为基准,竖向对称的另一组小球delay值为运动全程duration的1/2。而横向往后的每一组球的delay值依次递减,我设的是0.25秒。所有delay值都要设为负数,否则就真的delay了。
要注意的是小球的缩放运动与位移运动的起始位置其实是差了1/4的,所以缩放运动要再位移运动基础上加全程duration的1/4。
还有一个细节是底部的投影,我这里用的是background实现的,设置了一个菱形重复的背景,再用filter处理成模糊效果,最后控制background-position来达到横向位移效果。
React:
import cx from 'classnames';
import React from 'react';
import styles from './index.less';
interface ViewProps extends React.HTMLAttributes<HTMLSpanElement> {
active?: boolean;
}
export default class LoadingDna extends React.Component<ViewProps> {
public render() {
const { active, className, children, ...restProps } = this.props;
return (
<div className={cx(styles.box, className)} {...restProps}>
<div className={styles.inner}>
{new Array(2).fill(1).map((gp, gpId) => (
<div className={cx(styles.group, styles['group'+gpId])} key={gpId+'group'}>
{new Array(2).fill(1).map((item, index) => (
<div className={cx(styles.row, styles['row'+index])} key={index+'row'}>
{new Array(13).fill(1).map((it, ind) => (
<span className={cx(styles.dot, styles['dot'+ind])} key={ind+'dot'}>
<span className={styles.dotIn} key={ind+'dotIn'}>
<span className={styles.dotColor} key={ind+'dotColor'}></span>
</span>
</span>
))}
</div>
))}
</div>
))}
</div>
<div className={styles.background}></div>
</div>
);
}
}
Less:
@dotNum: 13;
@dotW: 20px;
@dotMrgR: 4px;
@dotMrgT: 2px;
@animatH: 50px;
@duration: 4s;
@interval: 0.25s;
@keyframes bgMove {
0% {
background-position-x: 0%;
}
100% {
background-position-x: 100%;
}
}
@keyframes dotMove {
0% {
transform: translateY(0px - @animatH);
}
50% {
transform: translateY(@animatH);
}
100% {
transform: translateY(0px - @animatH);
}
}
@keyframes dotScale {
0% {
transform: scale(1);
opacity: 0.8;
}
50% {
transform: scale(0.4);
opacity: 0.2;
}
100% {
transform: scale(1);
opacity: 0.8;
}
}
@keyframes dotBgColor {
0% {
background-color: #ff39a6;
}
25% {
background-color: #a000ff;
}
50% {
background-color: #d700ff;
}
75% {
background-color: #6039ff;
}
100% {
background-color: #ff39a6;
}
}
.box {
display: inline-block;
position: relative;
padding-bottom: 60px;
.background {
background: linear-gradient(-152deg, transparent 35%, rgba(96,57,255,0.25) 35%, rgba(96,57,255,0.25) 42%, transparent 42%) top right;
background-size: 50% 100%;
background-repeat: repeat;
position: absolute;
width: 100%;
height: 50px;
left: 0;
bottom: 0;
filter: blur(12px);
animation: bgMove @duration * 0.5 linear infinite;
animation-delay: -0.5s;
}
.inner {
display: flex;
align-items: center;
position: relative;
margin: @animatH 0;
}
.group {
&.group0 {
position: relative;
.row0 {
each(range(@dotNum), {
.dot:nth-child(@{value}) {
animation-name: dotMove;
animation-delay: @value * @interval - @duration - @duration;
.dotIn {
animation-name: dotScale;
animation-delay: @value * @interval - @duration - (@duration * 0.75);
}
.dotColor {
animation-delay: @value * @interval - @duration - (@duration * 0.75);
}
}
});
}
.row1 {
each(range(@dotNum), {
.dot:nth-child(@{value}) {
animation-name: dotMove;
animation-delay: @value * @interval - @duration - @duration;
.dotIn {
animation-name: dotScale;
animation-delay: @value * @interval - @duration - (@duration * 0.75);
}
.dotColor {
animation-delay: @value * @interval - @duration - (@duration * 0.6);
}
}
});
}
}
&.group1 {
position: absolute;
top: 0;
left: 0;
.row0 {
each(range(@dotNum), {
.dot:nth-child(@{value}) {
animation-name: dotMove;
animation-delay: @value * @interval - (@duration * 1.5);
.dotIn {
animation-name: dotScale;
animation-delay: @value * @interval - @duration - (@duration * 0.25);
}
.dotColor {
animation-delay: @value * @interval - @duration - (@duration * 0.25);
}
}
});
}
.row1 {
each(range(@dotNum), {
.dot:nth-child(@{value}) {
animation-name: dotMove;
animation-delay: @value * @interval - (@duration * 1.5);
.dotIn {
animation-name: dotScale;
animation-delay: @value * @interval - @duration - (@duration * 0.25);
}
.dotColor {
animation-delay: @value * @interval - @duration - (@duration * 0.1);
}
}
});
}
}
.row {
display: flex;
align-items: center;
transition: transform 0.3s ease-in-out;
.dot {
display: inline-block;
position: relative;
margin: @dotMrgT @dotMrgR;
animation-duration: @duration;
animation-iteration-count: infinite;
animation-timing-function: cubic-bezier(0.35, 0, 0.65, 1);
}
.dotIn {
display: inline-block;
animation-duration: @duration;
animation-iteration-count: infinite;
}
.dotColor {
display: inline-block;
width: @dotW;
height: @dotW;
border-radius: 100px;
background: #000;
animation-name: dotBgColor;
animation-duration: @duration;
animation-iteration-count: infinite;
}
}
}
&:hover {
.group0 {
.row0 {
transform: translateY(50%);
}
.row1 {
transform: translateY(-50%);
}
}
.group1 {
.row0 {
transform: translateY(50%);
}
.row1 {
transform: translateY(-50%);
}
}
}
}