Skip to content

Commit

Permalink
feat(add): animate 动画/动效组件 (#541)
Browse files Browse the repository at this point in the history
  • Loading branch information
junjun666 authored Dec 13, 2022
1 parent 7021753 commit 0b655e7
Show file tree
Hide file tree
Showing 19 changed files with 1,330 additions and 216 deletions.
11 changes: 11 additions & 0 deletions src/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,17 @@
"taro": true,
"author": "swag~jun"
},
{
"version": "1.0.0",
"name": "Animate",
"type": "component",
"cName": "动画/动效",
"desc": "给子元素添加动画效果",
"sort": 27,
"show": true,
"taro": true,
"author": "swag~jun"
},
{
"version": "1.0.0",
"name": "Ellipsis",
Expand Down
1 change: 0 additions & 1 deletion src/locales/zh-UG.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { BaseLang } from './base'

const zhUG: BaseLang = {
Expand Down
76 changes: 76 additions & 0 deletions src/packages/animate/__tests__/animate.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as React from 'react'
import { render, waitFor, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom'
import { AnimateType } from '../type'

import { Animate } from '../animate'

const testType = [
'shake',
'ripple',
'breath',
'float',
'jump',
'slide-right',
'slide-left',
'slide-top',
'slide-bottom',
'twinkle',
'flicker',
]

test('should change classname when using type prop', () => {
for (let i = 0; i < testType.length; i++) {
const typeProp = testType[i] as AnimateType
const { container } = render(<Animate type={typeProp} />)
const animate = container.querySelector('.nut-ani-container')
expect(animate).toHaveClass(`nut-animate-${typeProp}`)
}
})

test('trigger animate with loop', async () => {
const handleClick = jest.fn()
for (let i = 0; i < testType.length; i++) {
const typeProp = testType[i] as AnimateType
const { container } = render(
<Animate
type={typeProp}
action="click"
loop={true}
onClick={handleClick}
/>
)
const animate = container.querySelectorAll('.nut-ani-container')[0]
fireEvent.click(animate)
await waitFor(() => expect(handleClick).toBeCalled())

expect(animate).toHaveClass('loop')
expect(animate).toHaveClass(`nut-animate-${typeProp}`)

setTimeout(() => {
expect(animate).toHaveClass('loop')
expect(animate).toHaveClass(`nut-animate-${typeProp}`)
}, 1500)
}
})

test('trigger animate', async () => {
const handleClick = jest.fn()
for (let i = 0; i < testType.length; i++) {
const typeProp = testType[i] as AnimateType
const { container } = render(
<Animate type={typeProp} action="click" onClick={handleClick} />
)
const animate = container.querySelectorAll('.nut-ani-container')[0]
fireEvent.click(animate)
await waitFor(() => expect(handleClick).toBeCalled())

expect(animate).not.toHaveClass('loop')
expect(animate).toHaveClass(`nut-animate-${typeProp}`)

setTimeout(() => {
expect(animate).not.toHaveClass('loop')
expect(animate).not.toHaveClass(`nut-animate-${typeProp}`)
}, 1500)
}
})
284 changes: 284 additions & 0 deletions src/packages/animate/animate.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
.nut-animate {
.nut-ani-container {
display: inline-block;
}
/* Animation css */
[class*='nut-animate-'] {
animation-duration: 0.5s;
animation-timing-function: ease-out;
animation-fill-mode: both;
}

//右侧向左侧划入
.nut-animate-slide-right {
animation-name: slide-right;
}
@keyframes slide-right {
0% {
opacity: 0;
transform: translateX(100%);
}

100% {
opacity: 1;
transform: translateX(0);
}
}

//左侧向右侧划入
.nut-animate-slide-left {
animation-name: slide-left;
}
@keyframes slide-left {
0% {
opacity: 0;
transform: translateX(-100%);
}

100% {
opacity: 1;
transform: translateX(0);
}
}

//上面向下面划入
.nut-animate-slide-top {
animation-name: slide-top;
}
@keyframes slide-top {
0% {
opacity: 0;
transform: translateY(-100%);
}

100% {
opacity: 1;
transform: translateY(0);
}
}

//下面向上面划入
.nut-animate-slide-bottom {
animation-name: slide-bottom;
}
@keyframes slide-bottom {
0% {
opacity: 0;
transform: translateY(100%);
}

100% {
opacity: 1;
transform: translateY(0);
}
}

//抖动
.nut-animate-shake {
animation-name: shake;
}
@keyframes shake {
0%,
100% {
transform: translateX(0);
}

10% {
transform: translateX(-9px);
}

20% {
transform: translateX(8px);
}

30% {
transform: translateX(-7px);
}

40% {
transform: translateX(6px);
}

50% {
transform: translateX(-5px);
}

60% {
transform: translateX(4px);
}

70% {
transform: translateX(-3px);
}

80% {
transform: translateX(2px);
}

90% {
transform: translateX(-1px);
}
}

//心跳
.nut-animate-ripple {
animation-name: ripple;
}
@keyframes ripple {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}

//呼吸灯
.nut-animate-breath {
animation-name: breath;
animation-duration: 2700ms;
animation-timing-function: ease-in-out;
animation-direction: alternate;
}
@keyframes breath {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}

// 水波
.nut-animate-twinkle {
position: relative;
&::after,
&::before {
width: 60 * 1px;
height: 60 * 1px;
content: '';
box-sizing: border-box;
border: 4 * 1px solid rgba(255, 255, 255, 0.6);
position: absolute;
border-radius: calc(60 / 2) * 1px;
right: 50%;
margin-top: calc(-30 / 2) * 1px;
margin-right: calc(-60 / 2) * 1px;
z-index: 1;
transform: scale(0);
animation: twinkle 2s ease-out infinite;
}

&::after {
animation-delay: 0.4s;
}
}
@keyframes twinkle {
0% {
transform: scale(0);
}
20% {
opacity: 1;
}
50%,
100% {
transform: scale(1.4);
opacity: 0;
}
}

.nut-animate-flicker {
position: relative;
overflow: hidden;

&::after {
width: 100 * 1px;
height: 60 * 1px;
position: absolute;
left: 0;
top: 0;
opacity: 0.73;
content: '';
background-image: linear-gradient(
106deg,
rgba(232, 224, 255, 0) 24%,
#e8e0ff 91%
);
animation: flicker 1.5s linear infinite;
transform: skewX(-20deg);
filter: blur(3 * 1px);
}
}
@keyframes flicker {
0% {
transform: translateX(-100 * 1px) skewX(-20deg);
}
40%,
100% {
transform: translateX(150 * 1px) skewX(-20deg);
}
}

// jump-跳跃
.nut-animate-jump {
transform-origin: center center;
animation: jump 0.7s linear;
}
@keyframes jump {
0% {
animation-timing-function: ease-in;
transform: rotate(0deg) translateY(0);
}
25% {
animation-timing-function: ease-out;
transform: rotate(10deg) translateY(20 * 1px);
}
50% {
animation-timing-function: ease-in;
transform: rotate(0deg) translateY(-10 * 1px);
}
75% {
animation-timing-function: ease-out;
transform: rotate(-10deg) translateY(20 * 1px);
}
100% {
animation-timing-function: ease-in;
transform: rotate(0deg) translateY(0);
}
}

// 漂浮
.nut-animate-float {
position: relative;
animation-name: float-pop;
}
@keyframes float-pop {
0% {
top: 0px;
}

25% {
top: 1px;
}

50% {
top: 4px;
}

75% {
top: 1px;
}

100% {
top: 0px;
}
}

//循环
.loop {
animation-iteration-count: infinite;
}
}
Loading

0 comments on commit 0b655e7

Please sign in to comment.