Commit 6f702633 authored by Friends233's avatar Friends233

金币收集动效

parent c1911fe0
...@@ -14,3 +14,5 @@ ...@@ -14,3 +14,5 @@
**圆形转盘:CircleTurntableDemo** **圆形转盘:CircleTurntableDemo**
**金币收集动效:Collect**
...@@ -15,6 +15,7 @@ MD(); ...@@ -15,6 +15,7 @@ MD();
import LoadingDemo from "@src/pages/loadingDemo/loadingDemo"; import LoadingDemo from "@src/pages/loadingDemo/loadingDemo";
import HomeDemo from "@src/pages/homeDemo/homeDemo"; import HomeDemo from "@src/pages/homeDemo/homeDemo";
import CircleTurntableDemo from "./pages/CircleTurntableDemo/CircleTurntableDemo"; import CircleTurntableDemo from "./pages/CircleTurntableDemo/CircleTurntableDemo";
import Collect from "./pages/Collect";
/** /**
* 所有页面场景 * 所有页面场景
...@@ -22,7 +23,8 @@ import CircleTurntableDemo from "./pages/CircleTurntableDemo/CircleTurntableDemo ...@@ -22,7 +23,8 @@ import CircleTurntableDemo from "./pages/CircleTurntableDemo/CircleTurntableDemo
export const pageMap = { export const pageMap = {
loading: <LoadingDemo />, loading: <LoadingDemo />,
homePage: <HomeDemo/>, homePage: <HomeDemo/>,
CircleTurntableDemo:<CircleTurntableDemo /> CircleTurntableDemo:<CircleTurntableDemo />,
Collect:<Collect/>
} }
@observer @observer
......
{"preLoadImg":[],"asyncLoadImg":["转盘抽奖/主标题.png","转盘抽奖/关闭.png","转盘抽奖/抽奖.png","转盘抽奖/抽奖背景.png","转盘抽奖/转盘背景.png"]} {"preLoadImg":[],"asyncLoadImg":["common/goldBean.png","转盘抽奖/主标题.png","转盘抽奖/关闭.png","转盘抽奖/抽奖.png","转盘抽奖/抽奖背景.png","转盘抽奖/转盘背景.png"]}
\ No newline at end of file \ No newline at end of file
import React, { useEffect, useState } from "react"
import { render as reactRender } from "react-dom"
import './CollectAni.less'
/**
* 获取元素相对视窗原点的位置
* @param {HTMLElement} ele
*/
function getElementPos(ele) {
if (typeof ele === 'string') {
ele = document.getElementById(ele)
}
const { top, left, width, height } = ele.getBoundingClientRect()
return {
x: left + width/2,
y: top + height/2
}
}
const defaultProps = {
icon: '',
from: null,
to: null,
count: 10,
duration: 1000,
}
class CollectAni extends React.PureComponent {
/**
*
* @param {Object} props
* @param {String} props.icon
* @param {Object} props.fromPos
* @param {Object} props.toPos
* @param {Number} props.count
* @param {Number} props.duration
*/
constructor(props = defaultProps) {
super(props)
this.fromPos = this.props.fromPos
this.toPos = this.props.toPos
this.endCount = 0
this.collectRef = React.createRef()
}
renderElements() {
const { count, icon, duration, width, height } = this.props
const preDuration = Math.ceil(duration/count)
// const disX = this.toPos.x - this.fromPos.x
// const disY = this.toPos.y - this.fromPos.y
let i = 0
const iconElemList = []
while (i < count) {
iconElemList.push(
<FlyObject
width={width}
height={height}
key={i}
delay={preDuration * i}
duration={preDuration * 3}
pos={this.fromPos}
toPos={this.toPos}
icon={icon}
onTransitionEnd={this.onTransitionEnd}
/>
)
i++
}
return iconElemList
}
onTransitionEnd = () => {
this.endCount ++
if (this.endCount === this.props.count) { // 卸载组件
console.log(this.props.destroy)
typeof this.props.destroy === 'function' && this.props.destroy()
}
}
render() {
return (
<div ref={this.collectRef} className="KCollectAni">
{this.renderElements()}
</div>
)
}
}
CollectAni.show = function(props = {
icon: '',
from: null,
to: null,
width: 0,
height: 0,
count: 10,
duration: 1000,
}, callback) {
props.fromPos = getElementPos(props.from)
props.toPos = getElementPos(props.to)
const element = document.createElement('div')
document.body.appendChild(element)
// // 防止动画结束了DOM元素还在,这里兜底处理
const aniTimer = setTimeout(() => {
destroy()
}, props.duration + 500)
// 当所有的移动节点动画结束后调用
function destroy() {
document.body.removeChild(element)
if (aniTimer) clearTimeout(aniTimer)
typeof callback === 'function' && callback()
}
props.destroy = destroy
reactRender(<CollectAni {...props} />, element)
}
function FlyObject(props) {
const { delay, duration, pos, icon, width, height, toPos } = props
const [playing, setPlaying] = useState(false)
useEffect(() => {
const timer = setTimeout(() => {
setPlaying(true)
}, 50)
return function() {
clearTimeout(timer)
}
}, [])
function onTransitionEnd(e) {
e.target.style.display = 'none'
typeof props.onTransitionEnd === 'function' && props.onTransitionEnd()
}
return <img
src={icon}
style={{
width: width/100 + 'rem',
height: height/100 + 'rem',
left: playing ? toPos.x + 'px' : pos.x + 'px',
top: playing ? toPos.y + 'px' : pos.y + 'px',
transition: playing ? `all ${duration}ms ${delay}ms` : 'unset',
transform: 'translate(-50%, -50%)',
}}
onTransitionEnd={e => onTransitionEnd(e)}
/>
}
export default CollectAni
.KCollectAni {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 1;
img {
position: absolute;
transition-property: all;
transition-timing-function: ease-in;
transition-duration: 300ms;
}
}
\ No newline at end of file
import * as ReactDOM from 'react-dom'
// Let compiler not to search module usage
const fullClone = {
...ReactDOM,
}
const { version, render: reactRender, unmountComponentAtNode } = fullClone;
/**
* @type {typeof CreateRoot}
*/
let createRoot;
try {
const mainVersion = Number((version || '').split('.')[0]);
if (mainVersion >= 18) {
({ createRoot } = fullClone);
}
} catch (e) {
// Do nothing;
}
/**
*
* @param {Boolean} skip 是否跳过
*/
function toggleWarning(skip) {
const { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } = fullClone;
if (
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED &&
typeof __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED === 'object'
) {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.usingClientEntryPoint =
skip;
}
}
const MARK = '__rc_react_root__';
/**
* 新的渲染模式
* @param {React.ReactElement} node React节点
* @param {Element | DocumentFragment} container
*/
function modernRender(node, container) {
toggleWarning(true);
const root = container[MARK] || createRoot(container);
toggleWarning(false);
root.render(node);
container[MARK] = root;
}
/**
* 旧的渲染模式
* @param {React.ReactElement} node React节点
* @param {Element | DocumentFragment} container
*/
function legacyRender(node, container) {
reactRender(node, container);
}
/**
* 渲染
* @param {React.ReactElement} node React节点
* @param {Element | DocumentFragment} container
* @returns
*/
export function render(node, container) {
if (createRoot) {
modernRender(node, container);
return;
}
legacyRender(node, container);
}
// ========================= Unmount ==========================
/**
* 新的卸载方式
* @param {Element | DocumentFragment} container
* @returns
*/
async function modernUnmount(container) {
// Delay to unmount to avoid React 18 sync warning
return Promise.resolve().then(() => {
container[MARK]?.unmount();
delete container[MARK];
});
}
/**
* 旧的卸载方式
* @param {Element | DocumentFragment} container
*/
function legacyUnmount(container) {
unmountComponentAtNode(container);
}
/**
* 卸载
* @param {Element | DocumentFragment} container
* @returns
*/
export async function unmount(container) {
if (createRoot !== undefined) {
// Delay to unmount to avoid React 18 sync warning
return modernUnmount(container);
}
legacyUnmount(container);
}
import CollectAni from "@src/components/CollectAni/CollectAni";
import { observer } from "mobx-react";
import React from "react";
import { RES_PATH } from "../../../sparkrc";
import './index.less'
const Collect = () => {
const showAin = () => {
// 收集动效
CollectAni.show({
from: 'start', // 收集动画开始的节点的id
to: 'end', // 收集动画结束的节点的id
icon: RES_PATH + 'common/goldBean.png', // 收集动画图片路径
width: 36, // 收集动画图片的宽
height: 40, // 收集动画图片的高
count: 3, // 收集动画的图片数量
duration: 1000 // 收集动画总的持续时长
}, async () => { // 动画结束后的回调方法
console.log('结束')
})
}
return <div className="collect-wrapper" onClick={showAin}>
<div className="start" id="start">
开始位置
</div>
<div className="end" id="end">
结束位置
</div>
</div>
}
export default observer(Collect)
\ No newline at end of file
.collect-wrapper {
width: 100%;
height: 750px;
display: flex;
justify-content: space-between;
.start {
width: 100px;
height: 100px;
background-color: aqua;
margin-top: 100px;
}
.end {
width: 100px;
height: 100px;
background-color: rgb(11, 45, 45);
margin-top: 200px;
color: white;
}
}
\ No newline at end of file
/* /*
* @Author: super * @Author: super
* @Date: 2021-01-20 14:08:27 * @Date: 2021-01-20 14:08:27
* @LastEditTime: 2022-11-01 11:25:33 * @LastEditTime: 2022-11-01 16:52:28
* @LastEditors: Please set LastEditors * @LastEditors: Please set LastEditors
* @Description: * @Description:
*/ */
/** /**
* 弹窗优先级 可以是负数, 不写默认是10, 数值越小,层级越高 * 弹窗优先级 可以是负数, 不写默认是10, 数值越小,层级越高
*/ */
export const MODAL_INDEX = { export const MODAL_INDEX = {
// rank: 1, // rank: 1,
}; };
/** /**
* 接口错误消息配置 * 接口错误消息配置
*/ */
export function ERROR_MESSAGE(errorCode) { export function ERROR_MESSAGE(errorCode) {
let message = ""; let message = "";
switch (errorCode) { switch (errorCode) {
...@@ -22,7 +22,7 @@ export function ERROR_MESSAGE(errorCode) { ...@@ -22,7 +22,7 @@ export function ERROR_MESSAGE(errorCode) {
// break; // break;
case 999002: case 999002:
message = "活动已结束"; message = "活动已结束";
break; break;
case 500021: case 500021:
message = "没有昨日线下步数"; message = "没有昨日线下步数";
break; break;
...@@ -37,5 +37,6 @@ export function ERROR_MESSAGE(errorCode) { ...@@ -37,5 +37,6 @@ export function ERROR_MESSAGE(errorCode) {
} }
export const all_page = [ export const all_page = [
{ name: '原型转盘', page: 'CircleTurntableDemo' } { name: '原型转盘', page: 'CircleTurntableDemo' },
{ name: '金币收集动效', page: 'Collect' }
] ]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment