【端午节】新奇体验,我用react实现网页游戏的全过程(包括规则设计)
theme: channing-cyan highlight: atelier-lakeside-light
“我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛”
关于游戏的灵感来源
今年元宵节的时候,我玩的小游戏里面有限时任务,可以解锁节日限定物品,于是那几天我玩的很欢乐很积极。端午节到来之前,我想玩一下身份转换,从玩家转换到游戏策划。一个有趣的想法在脑海中逐渐清晰。
假如我是游戏策划
假如我是游戏策划,首先会对自己灵魂三连问:活动内容什么?活动怎么玩?活动奖励是什么?
现有大体的想法,然后再拆分到各个细节中去。
因为游戏中的一些场景搭配、日常活动名称、称号等借鉴了我最近沉迷的游戏《美人传》,所以这次的游戏仅供学习练习,不做任何商业用途。
产品视角
站在产品的角度思考活动设计,我的产品视角是这样的:
一入夏,就盼着假期,过了五一很快就会到端午,一想到端午就不由自主的想到美味的粽子。所以端午的活动就来了,包粽子。众所周知,包粽子需要糯米、粽叶等必备材料,而粽子的内馅有很多种,本次活动中需要的是红枣。所以包粽子的材料就选定了糯米、粽叶、红枣三种。(活动内容是什么)
游戏中有日常收集任务,每个收集任务掉落的材料都是固定的。活动期间一般会增加活动材料限时掉落,所以在活动期间,日常收集时会掉落包粽子需要的材料,不同收集任务掉落不同材料。(活动怎么玩)
粽子积累到一定数量就可以兑换节日限定物品。一般游戏中的节日限定物品都是精心设计的,但是由于时间和精力有限,我这次活动设计的比较简单,不同数量的粽子可以兑换不同的称号,最高称号为“荣宠万千”。(活动奖励是什么)
(^U^)ノ~YO,一切准备就绪,开始干活。
交互设计
大致画了一下设计草图,帮助理清楚布局思路。(第一次画,还有待提高。)
首页
日常任务
端午活动
功能设计
首页
内容
主要包括用户信息、任务入口、活动入口等展示。
称号规则
称号和糯米粽子数量对应如下:
| 称号 | 糯米粽子数量 | | ---- | --------------- | | 殿上佳人 | <50 | | 淑仪倾城 | >=50 && < 100 | | 花容初绽 | >=100 && < 200 | | 花成蜜就 | >=200 && < 300 | | 宠冠六宫 | >=300 && < 400 | | 凤仪千载 | >=400 |
功能实现
首页页面
文件路径:/home/index.jsx
```js /* * @description 首页 / import React from 'react'; import { useHistory } from 'react-router-dom'; import Avatar from '@/components/Avatar'; import FlowerCluster from '@/components/FlowerCluster'; import { Button } from 'antd-mobile'; import './index.less';
const Home = () => { const history = useHistory();
// 页面跳转 const goTo = path => { history.push(path); };
// 入口展示 const entranceContent = () => { return (
export default Home; ```
样式:/home/index.less
js
.home {
width: 100%;
height: 100vh;
position: relative;
background: #46272d;
&-head {
width: 100%;
height: 60px;
background: #f3a29f;
position: relative;
}
&-center {
width: 200px;
height: 200px;
z-index: 99;
margin-top: 60px;
}
&-bg {
width: 100%;
position: absolute;
top: 70px;
left: 0;
z-index: 10;
.door {
&-beam {
width: 100%;
height: 90px;
border-top:3px solid #9b6d59;
background: #825146;
position: relative;
.tiaoliang {
width: 100%;
height: 50px;
background: #4e2e29;
background-image: repeating-linear-gradient(45deg, transparent, transparent 13px, #9b6d59 13px, #9b6d59 15px), repeating-linear-gradient(-45deg, transparent, transparent 13px, #9b6d59 13px, #9b6d59 15px);
border-top: 5px solid #f5a672;
border-bottom: 5px solid #f5a672;
position: absolute;
top: 20px;
left: 0;
}
}
&-frame {
width: 100%;
height: 300px;
position: relative;
overflow: hidden;
.door-top {
width: 100%;
height: 30px;
border-top: 4px solid #f5a672;
border-bottom: 4px solid #f5a672;
background: #89544c;
position: absolute;
top: 0;
left: 0;
z-index: 99;
}
.door-line {
background: #673a35;
position: absolute;
z-index: 89;
&-left{
width: 15px;
height: 100%;
top: 0;
left: 0;
border-right: 2px solid #815345;
}
&-right{
width: 15px;
height: 100%;
top: 0;
right: 0;
border-left: 2px solid #815345;
}
&-bottom{
width: 100%;
height: 15px;
bottom: 0;
left: 0;
z-index: 87;
border-top: 2px solid #815345;
}
}
.door-frame {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
.stick-h {
width: 6px;
height: 100%;
background: #774747;
position: absolute;
top: 50px;
}
.stick-h1 {
left: 30px;
}
.stick-h2 {
left: 70px;
}
.stick-h3 {
left: 85px;
}
.stick-h4 {
left: 100px;
}
.stick-h5 {
left: 115px;
}
.stick-h6 {
left: 130px;
}
.stick-h7 {
right: 130px;
}
.stick-h8 {
right: 115px;
}
.stick-h9 {
right: 100px;
}
.stick-h10 {
right: 85px;
}
.stick-h11 {
right: 70px;
}
.stick-h12 {
right: 30px;
}
.stick-d {
width: 30px;
height: 6px;
background: #774747;
position: absolute;
}
.stick-d1 {
width: 100%;
top: 50px;
left: 0;
}
.stick-d2 {
top: 65px;
left: 86px;
}
.stick-d3 {
width: 20px;
top: 80px;
left: 70px;
}
.stick-d4 {
top: 65px;
right: 86px;
}
.stick-d5 {
width: 20px;
top: 80px;
right: 70px;
}
.stick-d6 {
width: 100%;
bottom: 30px;
left: 0;
}
}
.door-opening {
width: 300px;
height: 300px;
border-radius: 50%;
position: absolute;
top: 35px;
left: 50%;
margin-left: -150px;
background: #7c5655;
overflow: hidden;
&-center{
width: 250px;
height: 250px;
border-radius: 50%;
position: absolute;
top: 25px;
left: 25px;
background: #fff;
}
&-decorate {
width: 50px;
height: 80px;
border-radius: 50%;
background: #f3c068;
position: absolute;
}
&-decorate1 {
left: -30px;
top: 100px;
}
&-decorate2 {
left: 50%;
top: -43px;
margin-left: -25px;
transform: rotate(90deg);
}
&-decorate3 {
right: -30px;
top: 100px;
}
&-flowers {
position: absolute;
bottom: 55px;
right: 43px;
.flowercluster {
transform: scale(0.85);
}
}
}
}
}
.floor {
width: 100%;
height: 300px;
position: relative;
background: #946962;
overflow: hidden;
&-line {
width: 1px;
height: 100%;
background: linear-gradient( to bottom, #b48e5e 20%, #eebe88 40%, #fce49c 60%, #9f725a 80%, #f7c887 100%);
position: absolute;
top: 0;
}
&-line1 {
left: 0;
transform: rotate(10deg);
}
&-line2 {
left: 10%;
transform: rotate(10deg);
}
&-line3 {
left: 23%;
transform: rotate(5deg);
}
&-line4 {
left: 34%;
transform: rotate(2deg);
}
&-line5 {
left: 45%;
}
&-line6 {
right: 43%;
transform: rotate(-2deg);
}
&-line7 {
right: 32%;
transform: rotate(-5deg);
}
&-line8 {
right: 20%;
transform: rotate(-8deg);
}
&-line9 {
right: 10%;
transform: rotate(-10deg);
}
&-line10 {
right: 0;
transform: rotate(-10deg);
}
}
}
&-cat {
width: 200px;
height: 60px;
position: absolute;
top: 95px;
right: 10px;
.body {
width: 110px;
height: 50px;
background-color: #745341;
position: absolute;
top: -4px;
border-top-left-radius: 90px;
border-top-right-radius: 90px;
animation: catbody 10s none infinite;
}
@keyframes catbody {
5% {
transform: scaleY(1);
}
10% {
transform: scaleY(1.15);
}
15% {
transform: scaleY(1);
}
20% {
transform: scaleY(1.25);
}
25% {
transform: scaleY(1);
}
30% {
transform: scaleY(1.15);
}
40% {
transform: scaleY(1);
}
50% {
transform: scaleY(1.15);
}
}
.head {
width: 70px;
height: 34px;
background-color: #745341;
position: absolute;
top: 13px;
left: -45px;
border-top-left-radius: 70px;
border-top-right-radius: 70px;
}
.ear {
width: 0;
height: 0;
position: absolute;
left: 5px;
top: -4px;
border-left: 12px solid transparent;
border-right: 12px solid transparent;
border-bottom: 20px solid #745341;
transform: rotate(-30deg);
animation: catearleft 10s both infinite;
}
.ear-right {
top: -11px;
left: 21px;
animation: catearright 10s both infinite;
}
@keyframes catearleft {
0% {
transform: rotate(-20deg);
}
5% {
transform: rotate(-5deg);
}
15% {
transform: rotate(-15deg);
}
25% {
transform: rotate(-15deg);
}
35% {
transform: rotate(-30deg);
}
40% {
transform: rotate(-30deg);
}
45% {
transform: rotate(0deg);
}
50% {
transform: rotate(0deg);
}
80% {
transform: rotate(-15deg);
}
90% {
transform: rotate(-5deg);
}
100% {
transform: rotateZ(-5deg);
}
}
@keyframes catearright {
0% {
transform: rotateZ(-15deg);
}
15% {
transform: rotateZ(-20deg);
}
25% {
transform: rotateZ(-20deg);
}
30% {
transform: rotateZ(-30deg);
}
34% {
transform: rotateZ(-20deg);
}
38% {
transform: rotateZ(-30deg);
}
40% {
transform: rotateZ(-20deg);
}
42% {
transform: rotateZ(-20deg);
}
44% {
transform: rotateZ(-30deg);
}
45% {
transform: rotateZ(-20deg);
}
50% {
transform: rotateZ(-10deg);
}
55% {
transform: rotateZ(-10deg);
}
60% {
transform: rotateZ(-20deg);
}
61% {
transform: rotateZ(-30deg);
}
62% {
transform: rotateZ(-20deg);
}
63% {
transform: rotateZ(-20deg);
}
64% {
transform: rotateZ(-30deg);
}
65% {
transform: rotateZ(-20deg);
}
80% {
transform: rotateZ(-20deg);
}
90% {
transform: rotateZ(-15deg);
}
100% {
transform: rotateZ(-15deg);
}
}
.nose {
width: 5px;
height: 5px;
background-color: #dc9d90;
position: absolute;
bottom: 10px;
left: 30px;
border-radius: 50%;
}
.whisker {
width: 16px;
height: 10px;
position: absolute;
bottom: 5px;
left: 7px;
transform-origin: right;
}
.whisker::before,
.whisker::after {
content: '';
width: 100%;
position: absolute;
top: 0;
border: 1px solid #fff;
transform-origin: 100% 0;
transform: rotate(10deg);
}
.whisker::after {
transform: rotate(-20deg);
}
.whisker-left {
animation: catwhiskerleft 10s both infinite;
}
.whisker-right {
left: 27px;
bottom: 12px;
transform: rotate(180deg);
animation: catwhiskerright 10s both infinite;
}
@keyframes catwhiskerleft {
5% {
transform: rotate(0);
}
10% {
transform: rotate(0deg);
}
15% {
transform: rotate(-5deg);
}
20% {
transform: rotate(0deg);
}
25% {
transform: rotate(0deg);
}
30% {
transform: rotate(10deg);
}
40% {
transform: rotate(-5deg);
}
50% {
transform: rotate(10deg);
}
}
@keyframes catwhiskerright {
5% {
transform: rotate(180deg);
}
10% {
transform: rotate(190deg);
}
15% {
transform: rotate(180deg);
}
20% {
transform: rotate(175deg);
}
25% {
transform: rotate(190deg);
}
30% {
transform: rotate(180deg);
}
40% {
transform: rotate(185deg);
}
50% {
transform: rotate(175deg);
}
}
.tail {
width: 14px;
height: 100px;
position: absolute;
top: 42px;
right: 90px;
z-index: 99;
}
.tail-line {
width: 14px;
height: 60px;
background: #745341;
position: absolute;
left: 0;
top: 0;
z-index: 99;
}
.tail-round {
width: 48px;
height: 48px;
background: #745341;
position: absolute;
top: 36px;
left: -34px;
border-radius: 50%;
}
.tail-round::before {
content: '';
width: 20px;
height: 20px;
background: #946962;
position: absolute;
top: 14px;
left: 14px;
border-radius: 50%;
}
.tail-round::after {
content: '';
width: 48px;
height: 22px;
background: #946962;
position: absolute;
top: 0;
left: 0;
}
.tail-end {
width: 14px;
height: 10px;
background: #745341;
border-radius: 14px 14px 0 0;
position: absolute;
bottom: 39px;
left: -34px;
z-index: 99;
}
}
&-table {
width: 200px;
height: 20px;
background-color: #e3895e;
position: absolute;
top: 140px;
right: 80px;
border-radius: 20px;
z-index: 9;
}
&-entrance {
position: absolute;
top: 60px;
left: 35px;
.entrance-btn {
width: 180px;
line-height: 28px;
font-size: 16px;
font-weight: 600;
color: #fff;
border: 0;
background-image: linear-gradient(to right, #ed6ea0, #ec8c69, #f7186a, #FBB03B);
background-size: 300% 100%;
box-shadow: 0 4px 15px 0 #ed6ea0;
margin-bottom: 20px;
animation: 5s ease-in-out entrance infinite;
}
}
}
@keyframes entrance {
0% {
background-image: linear-gradient(to right, #ed6ea0, #ec8c69, #f7186a, #FBB03B);
background-size: 300% 100%;
}
100% {
background-image: linear-gradient(to right, #FBB03B, #ec8c69, #f7186a, #ed6ea0);
background-position: 100% 0;
}
}
头像组件
文件路径:/components/Avatar/index.jsx
```js /* * @description 头像组件 / import React from 'react'; import './index.less'; import util from '../../utils/util';
const Avatar = () => { const userInfo = util.getUserInfo() || {};
const getDesignationByZongziNum = () => { const festival = userInfo.festival ? userInfo.festival : {}; const zongzi = festival.zongzi ? festival.zongzi : 0; let name = '殿上佳人'; if (zongzi < 50) { name = '殿上佳人'; } else if (zongzi <= 100) { name = '淑仪倾城'; } else if (zongzi <= 200) { name = '花容初绽'; } else if (zongzi <= 300) { name = '花成蜜就'; } else if (zongzi <= 400) { name = '宠冠六宫'; } else if (zongzi > 400) { name = '凤仪千载'; } return name; };
return (
export default Avatar; ```
样式:/components/Avatar/index.less
```js .avatar { width: 100%; height: 60px; position: relative; &-img { width: 70px; height: 70px; border-radius: 50%; z-index: 99; position: absolute; left: 10px; bottom: -20px; border: 3px solid #c03e34; } &-nickname { height: 24px; line-height: 24px; background: #ff8fa7; border-radius: 24px; position: absolute; left: 60px; top: 35px; color: #fff; font-size: 14px; font-weight: 300; text-align: center; z-index: 89; border:1px solid #fff; padding: 0 10px 0 25px; } &-designation { width: 110px; height: 32px; line-height: 32px; position: absolute; right: 0; top: 15px; z-index: 89; text-align: center; padding-left: 10px; border-radius: 28px 0 0 0; background-color: #f0ecfc; background-image: linear-gradient(315deg,#ffeded 0,#fed6d6 74%); span { font-size: 18px; color: #fff; text-shadow: 1px 1px #ffb53a,-1px -1px #ffb53a,1px -1px #ffb53a,-1px 1px #ffb53a; }
} &-flower { position: absolute; top: 5px; left: 10px; transform: rotate(-30deg) scale(0.8); &-leaf { position: absolute; border-radius: 51% 49% 47% 53%; background-color: #a7ffee; background-image: linear-gradient(to top, #ffeded 15%, #ff8fa7 100%); transform-origin: bottom center; opacity: 0.9; box-shadow: inset 0 0 6px #fed6d6; } &-leaf1 { width: 28px; height: 34px; bottom: -10px; left: -14px; transform: translate(-10%, 1%) rotateY(40deg) rotateX(-50deg); } &-leaf2 { width: 23px; height: 32px; bottom: -4px; left: -5px; transform: translate(-50%, -4%) rotateX(40deg); } &-leaf3 { width: 28px; height: 30px; bottom: -3px; left: 0px; transform: translate(-90%, 0%) rotateY(45deg) rotateX(50deg); } &-leaf4 { width: 28px; height: 24px; bottom: -5px; left: 6px; transform: translate(-61%, -19%) rotateX(67deg) rotate(193deg); } &-leaf5 { width: 28px; height: 25px; bottom: -5px; left: -4px; transform: translate(-55%, -20%) rotateX(71deg) rotate(211deg); } &-circle { position: absolute; left: -12px; top: -10px; width: 16px; height: 8px; border-radius: 50%; background-color: #fdfd8e; } } } ```
花丛组件
这个是参考的网站是的,参考地址我放到了文章末尾。
文件路径:/components/FlowerCluster/index.jsx
```js /* * @description 花丛组件 / import React from 'react'; import './index.less';
const FlowerCluster = () => { return (
样式:/components/FlowerCluster/index.less
js
.flowercluster {
width: 60px;
height: 60px;
.flowers:after {
content: '';
position: absolute;
width: 60px;
height: 35px;
background-color: rgba(0, 0, 0, 0.1);
bottom: 0;
z-index: -2;
border-radius: 100%;
left: -10px;
bottom: -15px;
}
.flower-leaves {
position: relative;
width: 100%;
height: 20px;
background-color: #a8e6ba;
border-radius: 100% 10%;
top: 80%;
left: 5px;
box-shadow: -1px 1px black, 1px 1px black, 1px -1px black;
}
.flower-leaves:before,
.flower-leaves:after {
content: '';
position: absolute;
background-color: #a8e6ba;
}
.flower-leaves:before {
width: 60px;
height: 20px;
border-radius: 100% 10%;
transform: rotate(30deg);
right: 10px;
box-shadow: -1px 1px black, 1px 1px black, 1px -1px black;
}
.flower-leaves:after {
width: 50px;
height: 20px;
border-radius: 100% 10%;
transform: rotate(15deg);
top: 2px;
}
.flower {
position: absolute;
width: 30px;
height: 30px;
}
.flower:after {
content: '';
position: absolute;
width: 8px;
height: 8px;
border-radius: 100%;
left: 6px;
top: 8px;
background-image: radial-gradient(8px 8px at center, #9379aa 30%, #521c81 41%, 60%, transparent);
}
.flower > .petal {
position: absolute;
width: 10px;
height: 10px;
background-color: #f8f8ff;
background-image: linear-gradient(45deg, #f8f8ff, #d3cce3);
border-radius: 50% 80%;
box-shadow: -0.04em -0.04em purple, -0.05em -0.05em black;
}
.flower > .petal:nth-child(1) {
transform: rotate(40deg);
left: 5px;
top: 2px;
}
.flower > .petal:nth-child(2) {
transform: rotate(-20deg);
top: 6px;
left: 0;
}
.flower > .petal:nth-child(3) {
transform: rotate(-90deg);
top: 12px;
left: 2px;
}
.flower > .petal:nth-child(4) {
transform: rotate(180deg);
top: 12px;
left: 10px;
}
.flower > .petal:nth-child(5) {
transform: rotate(100deg);
top: 5px;
left: 10px;
}
.bunch .flower:nth-child(1) {
left: 33px;
transform: scale(1.5) rotate(30deg);
}
.bunch .flower:nth-child(2) {
left: 10px;
transform: scale(1.5) rotate(-20deg);
}
.bunch .flower:nth-child(3) {
left: 25px;
top: 40px;
transform: scale(1.5) rotate(5deg);
}
}
最终UI
设计为古代的室内,参考的《美人传》小游戏中的UI设计,包括木质的墙壁、门和地板。除此之外还加了一些动画效果增加趣味性:
- 称号上面加了一个花朵做装饰;
- 任务和活动入口上加了光效闪动的效果;
- 地板上的猫咪耳朵和肚子随着呼吸而动;
日常任务
日常任务收集规则
- 每天0点开始进行资源生产,每个小时生产1万资源,不足1个小时的时候不产生,满足1个小时的时候产生;
- 可以进行资源收集,每次收集完成,对应的资源值进行叠加;
- 不同资源收集时,随机掉落不同的活动材料。对应如下:
| 任务名称 | 活动材料名称 | 活动材料数量 | | ---- | ------ | ------ | | 开源节流 | 粽叶 | 5~10 | | 助宫易物 | 糯米 | 5~10 | | 布施济民 | 红枣 | 2~5 |
功能实现
日常页面
文件路径:/tasks/index.jsx
```js /* * @description 日常任务 / import React, { useState, useEffect } from 'react'; import classnames from 'classnames'; import moment from 'moment'; import Back from '@/components/Back'; import Flower from '@/components/Flower'; import FlowerTree from '@/components/FlowerTree'; import { Modal } from 'antd-mobile'; import { QuestionCircleFill, KoubeiFill, FireFill, HeartFill } from 'antd-mobile-icons'; import util from '../../utils/util'; import './index.less';
const Tasks = () => {
const userInfo = util.getUserInfo() || {};
const [tasksObj, setTasksObj] = useState(
userInfo.tasks
? userInfo.tasks
: {
zheng: 0,
cai: 0,
mei: 0,
creatAt: 0,
},
);
const listInit = [
{
key: 'zheng',
title: '政',
name: '开源节流',
num: 0,
harvestFalg: true,
taskKey: 'zongye',
icon:
const [list, setList] = useState(listInit);
// 获取当前内务展示数据 const getNewNum = () => { // 梯龄换算成月 const newData = new Date(); let diffData = tasksObj.creatAt; if (!tasksObj.creatAt) { // 如果收获时间默认活动开始时间 diffData = moment('2022-06-01'); } let hour = moment(newData).diff(moment(diffData), 'hours');
console.log(hour, 'hour');
let numCurr = hour * 1000;
const listInit = [...list];
listInit.map(item => {
item.num += numCurr;
});
setList(listInit);
};
useEffect(() => { getNewNum(); }, []);
// 获取随机数 const getRandomNumber = key => { const randomObj = { zheng: [5, 10], cai: [5, 10], mei: [2, 5], }; const randomItem = randomObj[key]; const m = randomItem[1]; const n = randomItem[0]; let randomNum = Math.random() * (m - n) + n; randomNum = Math.round(randomNum); console.log(randomNum, 'randomNum'); return randomNum; };
// 收获 const handleHarvest = index => { const newData = new Date(); let userInfoInit = { ...userInfo }; const handleList = [].concat(list); let item = handleList[index]; let tasksObjInit = { ...tasksObj }; tasksObjInit.creatAt = newData; const festivalObjInit = userInfo.festival ? userInfo.festival : { nuomi: 0, zongye: 0, hongzao: 0, zongzi: 0, }; // 收获操作 if (item.harvestFalg) { tasksObjInit[item.key] += item.num; item.num = 0; festivalObjInit[item.taskKey] = getRandomNumber(item.key); // 设置缓存 userInfoInit.festival = festivalObjInit; userInfoInit.tasks = tasksObjInit; util.saveUserInfo(userInfoInit); setList(list); setTasksObj(tasksObjInit); } item.harvestFalg = !item.harvestFalg; setList(handleList); };
// 顶部提示 const headTip = () => { return Modal.show({ title: '内务', content: (
内务分为“开源节流”,“助宫易物”,“布施济民”三种类型,分别可以获得铜币、珍品和名望。
打理内务有一定几率获得包粽子的材料。
开源节流有一定几率获得粽叶。
助宫易物有一定几率获得糯米。
布施济民有一定几率获得红枣。
// 将数据除以10000进行展示 const getTaskNumContent = num => { num = num / 10000; return num; };
return (
export default Tasks; ```
样式:/tasks/index.less
```js .tasks { width: 100%; max-width: 100%; height: 100vh; background: #ffe7e7; padding-top: 36px; position: relative; overflow: hidden; &-info { width: 70%; position: absolute; top: 15px; right: 5px; display: flex; justify-content: space-between; align-items: center; &-item { width: 28%; height: 17px; border-radius: 0 20px 20px 0; background: #a5888c; position: relative; span { font-size: 12px; color: #fff; line-height: 17px; text-align: center; position: absolute; left: 10px; top: 0; z-index: 99; } &-icon { position: absolute; top: -3px; left: -16px; width: 22px; height: 22px; border-radius: 50%; background: #a5888c; z-index: 89; display: flex; justify-content: center; align-items: center; } } } &-head { width: 100%; height: 50px; display: flex; justify-content: flex-start; align-items: center; &-tip { margin-left: 80px; margin-right: 40px; } &-title { color: #a08cc9; line-height: 50px; font-size: 26px; text-align: center; font-weight: 500; } } &-modal { width: 100%; padding: 0 5px; &-title { position: relative; height: 22px; line-height: 22px; text-align: center; margin-bottom: 10px; color: #af8368; &::before { content: ''; width: 40px; height: 2px; background: #eec2c1; position: absolute; top: 10px; left: 15px; border-radius: 2px; } &::after { content: ''; width: 40px; height: 2px; background: #eec2c1; position: absolute; top: 10px; right: 15px; border-radius: 2px; } } p { line-height: 1.4; font-weight: 300; font-size: 13px; position: relative; padding-left: 10px; &::before { content: ''; width: 4px; height: 4px; background: #af8368; border-radius: 50%; position: absolute; top: 6px; left: -2px;
}
}
} &-list { display: flex; justify-content:space-between; padding: 0 15px; margin-top: 25px; z-index: 20; } &-item { width: 30%; height: 340px; border: 5px solid #ffb4c0; border-radius: 0 0 30px 30px; position: relative; &:nth-child(2) { .tasks-item-title { background: #8fc4f6; } } &:nth-child(3) { .tasks-item-title { background: #da9ce9; } } &-top { position: absolute; left: -13px; top: -11px; height: 10px; width: 124%; background: #fca0ab; border: 1px solid #f5d896; border-radius: 10px; } &-title { position: absolute; left: 2px; top: 2px; width: 42px; height: 42px; border: 2px solid #fff; background: #fcaf5d; color: #fff; font-size: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; } &-name { position: absolute; top: 60px; left: 7px; width: 80px; height: 80px; transform: scale(0.85); span { display: block; width: 40px; line-height: 15px; font-size: 15px; color: #b67b53; position: absolute; top: 43%; left: 34%; z-index: 99; } .name-circular { width: 36px; height: 36px; background: #ffb4c0; border-radius: 50%; position: absolute; } .name-circular1 { top: 3px; left: 20px; } .name-circular2 { top: 20px; left: 46px; } .name-circular3 { top: 49px; left: 38px; } .name-circular4 { top: 51px; left: 11px; } .name-circular5 { top: 25px; left: -1px; } .name-circular6 { width: 45px; height: 45px; top: 26px; left: 20px; background: #ffe7e7; } } &-num { position: absolute; bottom: 120px; left: 5px; width: 90%; height: 22px; line-height: 22px; border: 1px solid #f6e2db; background: #fff; color: #89775f; font-size: 14px; font-weight: 300; border-radius: 22px; text-align: center; } &-btn { position: absolute; bottom: 30px; left: 15px; width: 64px; height: 64px; line-height: 64px; border: 1px solid #fed18d; background: #fff6d6; border-radius: 50%; text-align: center; &::after { content: ''; width: 56px; height: 56px; background: #fed18d; border-radius: 50%; position: absolute; top: 3px; left: 3px; z-index: 89; } span { position: absolute; top: 0; left: 0; width: 100%; height: 100%; color: #fff; font-size: 16px; z-index: 99; } .btn-flower1 { position: absolute; top: -6px; left: 36px; z-index: 990; transform: scale(0.9); } .btn-flower2 { position: absolute; top: -2px; left: 40px; z-index: 990; transform: scale(0.6); } &.inactive { border: 1px solid #e5c7fd; &::after { background: #e5c7fd; } } } &::before { content: ''; width: 60px; height: 45px; background: #ffe7e7; position: absolute; bottom: -49px; left: 20px; z-index: 99; border-radius: 0; } &::after { content: ''; width: 60px; height: 60px; background: #ffe7e7; border: 5px solid #ffb4c0; border-radius: 50%; position: absolute; bottom: -44px; left: 20px; z-index: 98; } } &-footer { position: absolute; top: 480px; left: -10%; background: #ffc3d2; width: 120%; height: 100px; border-radius: 0 0 50% 50%; z-index: 1; &::before { position: absolute; bottom: 10px; left: 10px; background: #ffc3d2; width: 200px; height: 200px; } &::after { content: ''; position: absolute; bottom: 20px; left: -10%; background: #ffe7e7; width: 120%; height: 100px; border-radius: 0 0 50% 50%; } } &-tree { position: absolute; top: 88%; left: 60px; z-index: 999; } &-rule { width: 200px; position: absolute; top: 88%; right: 10px; &-title { width: 70px; height: 70px; line-height: 70px; position: absolute; top: 0; left: 0; background: #fff; border: 2px solid #f8d4d6; border-radius: 50%; text-align: center; z-index: 90; span { position: absolute; top: 0; left: 0; width: 100%; height: 100%; color: #fff; font-size: 18px; z-index: 99; } &::after { content: ''; width: 60px; height: 60px; background: #deb4fc; border-radius: 50%; position: absolute; top: 3px; left: 3px; z-index: 89; } } &-text { width: 140px; line-height: 36px; font-size: 15px; font-weight: 300; color: #e34f4b; text-align: center; position: absolute; left: 56px; top: 16px; border-radius: 0 36px 36px 0; background: #fbf1ef; z-index: 80; } } } ```
返回组件
文件路径:/components/Back/index.jsx
```js /* * @description 回退按钮组件 / import React from 'react'; import PropTypes from 'prop-types'; import { useHistory } from 'react-router-dom'; import './index.less';
const Back = ({ ...props }) => { const history = useHistory(); const { path } = props;
// 点击事件 const handleClick = () => { history.push(path); };
return (
Back.defaultProps = { path: '/home', };
export default Back; ```
样式:/components/Back/index.less
js
.back {
width: 56px;
height: 56px;
background: #f69bad;
border: 2px solid #fef4f3;
border-radius: 50%;
position: absolute;
top: 10px;
left: 10px;
&-left {
position: absolute;
top: 21px;
left: 10px;
&::before {
content: '';
width: 22px;
height: 3px;
background: #fff;
position: absolute;
top: 10px;
left: 0;
transform: rotate(30deg);
border-radius: 2px;
}
&::after {
content: '';
width: 22px;
height: 3px;
background: #fff;
position: absolute;
top: -1px;
left: 0;
transform: rotate(-30deg);
border-radius: 2px;
}
}
&-right {
position: absolute;
top: 27px;
left: 27px;
&::before {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 0;
border-bottom: 4px solid #fff;
border-right: 8px solid transparent;
border-left: 8px solid transparent;
}
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
border-top: 4px solid #fff;
border-right: 8px solid transparent;
border-left: 8px solid transparent;
}
}
}
花朵组件
文件路径:/components/Flower/index.jsx
```js /* * @description 花朵组件 / import React from 'react'; import './index.less';
const Flower = () => { return (
样式:/components/Flower/index.less
js
.flower {
width: 50px;
height: 50px;
transform: rotate(-3deg);
&-leaf {
position: absolute;
width: 6px;
height: 8px;
border-radius: 51% 49% 47% 53%;
background-color: #a7ffee;
background-image: linear-gradient(to top, #ffeded 15%, #ff8fa7 100%);
}
&-leaf1 {
top: 2px;
left: 2px;
transform: rotate(-3deg);
z-index: 999;
}
&-leaf2 {
top: 5px;
left: 7px;
transform: rotate(60deg);
z-index: 998;
}
&-leaf3 {
top: 10px;
left: 6px;
transform: rotate(160deg);
z-index: 997;
}
&-leaf4 {
top: 11px;
left: 0px;
transform: rotate(200deg);
z-index: 996;
}
&-leaf5 {
top: 6px;
left: -2px;
transform: rotate(-75deg);
z-index: 995;
}
&-circle {
position: absolute;
left: 3px;
top: 8px;
width: 4px;
height: 4px;
border-radius: 50%;
background-color: #fff3b4;
border: 1px solid #fff;
}
}
开满花的树组件
这个是参考的网站是的,参考地址我放到了文章末尾。
文件路径:/components/FlowerTree/index.jsx
```js /* * @description 开满花的树组件 / import React from 'react'; import './index.less';
const FlowerTree = () => { return (
样式:/components/FlowerTree/index.less
js
.flowertree {
width: 60px;
height: 200px;
transform: scale(1.1);
.trunk {
width: 58%;
height: 30%;
background-color: #df916a;
border-left: 0.09em solid black;
border-right: 0.09em solid black;
box-shadow: inset 0 30px 10px #a64f24;
}
.trunk:after {
content: '';
position: absolute;
width: 60px;
height: 28px;
background-color: rgba(0, 0, 0, 0.2);
top: 40px;
z-index: -2;
right: 15px;
border-radius: 100%;
}
.trunk .roots {
position: relative;
z-index: -1;
top: 95%;
display: flex;
justify-content: center;
align-items: center;
width: 110%;
height: auto;
left: -5%;
}
.trunk .roots .root {
flex: 1 1 0;
background-color: #dd8b62;
height: 15px;
margin: 0 -1px;
border-radius: 10px;
}
.trunk .roots .root:nth-child(1) {
transform: rotate(30deg);
}
.trunk .roots .root:nth-child(2) {
transform: rotate(15deg);
}
.trunk .roots .root:nth-child(3) {
transform: rotate(0deg);
}
.trunk .roots .root:nth-child(4) {
transform: rotate(-15deg);
}
.trunk .roots .root:nth-child(5) {
transform: rotate(-30deg);
}
.trunk .roots .root:first-child {
box-shadow: -1px 1px #d0632d, -0.1em 1px black;
}
.trunk .roots .root:nth-child(n + 2):nth-child(-n + 4) {
box-shadow: 0 1px #d0632d, 0em 1px black;
}
.trunk .roots .root:last-child {
box-shadow: 1px 1px #d0632d, 0.1em 1px black;
}
.leaves,
.cherry-blossoms {
position: relative;
width: 60px;
height: 60px;
top: -120px;
left: -12px;
background-color: #4cbda4;
border-radius: 100%;
box-shadow: inset 4px -10px #41af97;
border: 1px solid #3a9c87;
border: 0.1em solid black;
}
.leaves:before,
.cherry-blossoms:before,
.leaves:after,
.cherry-blossoms:after {
content: '';
position: absolute;
width: 60px;
height: 60px;
background-color: #4cbda4;
border-radius: 100%;
top: 35px;
border: 1px solid #3a9c87;
border: 0.1em solid black;
border-top: 0;
border-bottom: 2px solid #91451f;
box-shadow: inset 4px -10px #41af97;
}
.leaves:before,
.cherry-blossoms:before {
left: -20px;
}
.leaves:after,
.cherry-blossoms:after {
left: 20px;
}
.leaves .leaf,
.cherry-blossoms .leaf {
position: absolute;
width: 10px;
height: 15px;
background-color: #4cbda4;
background-image: linear-gradient(to bottom, #097465, transparent);
border-radius: 10% 80%;
border-bottom: 1px solid #2c7766;
border-right: 1px solid #2c7766;
transform: scale(0.5);
}
.leaves .leaf:before,
.cherry-blossoms .leaf:before,
.leaves .leaf:after,
.cherry-blossoms .leaf:after {
content: '';
position: absolute;
background-color: #4cbda4;
background-image: linear-gradient(to bottom, #008b79, transparent);
width: 7px;
height: 12px;
border-radius: 10% 80%;
border-right: 1px solid #338a76;
}
.leaves .leaf:before,
.cherry-blossoms .leaf:before {
left: -3px;
transform: rotate(40deg);
border-left: 1px solid #338a76;
}
.leaves .leaf:after,
.cherry-blossoms .leaf:after {
left: 4px;
top: -2px;
transform: rotate(-40deg);
border-bottom: 1px solid #338a76;
}
.leaves .leaf:nth-child(1),
.cherry-blossoms .leaf:nth-child(1) {
top: 85px;
transform: scale(0.8) rotate(-25deg);
}
.leaves .leaf:nth-child(2),
.cherry-blossoms .leaf:nth-child(2) {
top: 80px;
left: -15px;
}
.leaves .leaf:nth-child(3),
.cherry-blossoms .leaf:nth-child(3) {
top: 85px;
left: 15px;
transform: scale(1.1) rotate(-25deg);
}
.leaves .leaf:nth-child(4),
.cherry-blossoms .leaf:nth-child(4) {
top: 80px;
left: 25px;
z-index: 3;
}
.leaves .leaf:nth-child(5),
.cherry-blossoms .leaf:nth-child(5) {
top: 85px;
left: 60px;
z-index: 2;
transform: scale(0.9) rotate(70deg);
}
.leaves .leaf:nth-child(6),
.cherry-blossoms .leaf:nth-child(6) {
top: 80px;
left: 45px;
z-index: 2;
transform: scale(1.2) rotate(50deg);
}
.leaves .leaf:nth-child(7),
.cherry-blossoms .leaf:nth-child(7) {
top: 72px;
left: -20px;
}
.leaves .leaf:nth-child(8),
.cherry-blossoms .leaf:nth-child(8) {
top: 75px;
left: -1px;
transform: scale(1.02) rotate(-25deg);
}
.leaves .leaf:nth-child(9),
.cherry-blossoms .leaf:nth-child(9) {
top: 70px;
left: 10px;
}
.leaves .leaf:nth-child(10),
.cherry-blossoms .leaf:nth-child(10) {
top: 55px;
left: -20px;
transform: scale(1.5) rotate(18deg);
}
.leaves .leaf:nth-child(11),
.cherry-blossoms .leaf:nth-child(11) {
top: 60px;
transform: scale(0.9);
left: -5px;
}
.leaves .leaf:nth-child(12),
.cherry-blossoms .leaf:nth-child(12) {
z-index: 2;
left: 28px;
top: 60px;
}
.leaves .leaf:nth-child(13),
.cherry-blossoms .leaf:nth-child(13) {
z-index: 2;
left: 40px;
top: 70px;
transform: rotate(40deg);
}
.leaves .leaf:nth-child(14),
.cherry-blossoms .leaf:nth-child(14) {
z-index: 2;
left: 55px;
top: 70px;
transform: rotate(60deg) scale(1.3);
}
.leaves .leaf:nth-child(15),
.cherry-blossoms .leaf:nth-child(15) {
z-index: 2;
left: 25px;
top: 70px;
transform: rotate(-20deg) scale(1.1);
}
.leaves .leaf:nth-child(16),
.cherry-blossoms .leaf:nth-child(16) {
z-index: 2;
left: 70px;
top: 70px;
transform: rotate(50deg) scale(0.7);
}
.leaves .leaf:nth-child(17),
.cherry-blossoms .leaf:nth-child(17) {
z-index: 2;
left: 70px;
top: 60px;
transform: scale(0.6) rotate(60deg);
}
.leaves .leaf:nth-child(18),
.cherry-blossoms .leaf:nth-child(18) {
z-index: 2;
left: 50px;
top: 60px;
transform: scale(0.7) rotate(60deg);
}
.leaves .leaf:nth-child(19),
.cherry-blossoms .leaf:nth-child(19) {
z-index: 2;
left: 65px;
top: 48px;
transform: scale(1.5) rotate(50deg);
}
.leaves .leaf:nth-child(20),
.cherry-blossoms .leaf:nth-child(20) {
z-index: 2;
left: 40px;
top: 50px;
transform: scale(0.8) rotate(70deg);
}
.leaves .leaf:nth-child(21),
.cherry-blossoms .leaf:nth-child(21) {
z-index: 2;
left: 58px;
top: 35px;
transform: scale(0.7) rotate(60deg);
}
.leaves .leaf:nth-child(22),
.cherry-blossoms .leaf:nth-child(22) {
z-index: 2;
left: 20px;
top: 55px;
transform: scale(1) rotate(60deg);
}
.leaves .leaf:nth-child(23),
.cherry-blossoms .leaf:nth-child(23) {
z-index: 2;
left: 40px;
top: 60px;
transform: scale(0.4) rotate(60deg);
}
.leaves .leaf:nth-child(24),
.cherry-blossoms .leaf:nth-child(24) {
z-index: 2;
left: 10px;
top: 60px;
transform: scale(0.6) rotate(-10deg);
}
.leaves .leaf:nth-child(25),
.cherry-blossoms .leaf:nth-child(25) {
left: -12px;
top: 38px;
transform: scale(1) rotate(60deg);
}
.leaves .leaf:nth-child(26),
.cherry-blossoms .leaf:nth-child(26) {
left: -5px;
top: 45px;
transform: scale(0.8) rotate(60deg);
}
.leaves .leaf:nth-child(27),
.cherry-blossoms .leaf:nth-child(27) {
left: 8px;
top: 52px;
transform: scale(1) rotate(-10deg);
}
.leaves .leaf:nth-child(28),
.cherry-blossoms .leaf:nth-child(28) {
z-index: 2;
left: 50px;
top: 52px;
transform: scale(1) rotate(-10deg);
}
.leaves .leaf:nth-child(29),
.cherry-blossoms .leaf:nth-child(29) {
left: 48px;
top: 10px;
transform: scale(1) rotate(-10deg);
}
.leaves .leaf:nth-child(30),
.cherry-blossoms .leaf:nth-child(30) {
left: 30px;
top: -5px;
transform: scale(0.8) rotate(-10deg);
}
.leaves .leaf:nth-child(31),
.cherry-blossoms .leaf:nth-child(31) {
left: 20px;
top: -5px;
transform: scale(0.5) rotate(-10deg);
}
.leaves .leaf:nth-child(32),
.cherry-blossoms .leaf:nth-child(32) {
left: 40px;
top: -3px;
transform: scale(0.5) rotate(-10deg);
}
.leaves .leaf:nth-child(33),
.cherry-blossoms .leaf:nth-child(33) {
left: 10px;
top: 10px;
transform: scale(0.7) rotate(-10deg);
}
.leaves .leaf:nth-child(34),
.cherry-blossoms .leaf:nth-child(34) {
left: 0;
top: 25px;
transform: scale(1) rotate(-10deg);
}
.leaves .leaf:nth-child(35),
.cherry-blossoms .leaf:nth-child(35) {
left: 20px;
top: 30px;
transform: scale(1.2) rotate(0deg);
}
.leaves .leaf:nth-child(36),
.cherry-blossoms .leaf:nth-child(36) {
left: 50px;
top: 30px;
transform: scale(1.2) rotate(60deg);
}
.leaves .leaf:nth-child(37),
.cherry-blossoms .leaf:nth-child(37) {
left: 50px;
top: 30px;
transform: scale(1.2) rotate(60deg);
}
.leaves .leaf:nth-child(38),
.cherry-blossoms .leaf:nth-child(38) {
left: 50px;
top: 20px;
transform: scale(1) rotate(40deg);
}
.leaves .leaf:nth-child(39),
.cherry-blossoms .leaf:nth-child(39) {
left: 40px;
top: 23px;
transform: scale(0.8) rotate(50deg);
}
.leaves .leaf:nth-child(40),
.cherry-blossoms .leaf:nth-child(40) {
left: 30px;
top: 20px;
transform: scale(0.5) rotate(-20deg);
}
.leaves .leaf:nth-child(40),
.cherry-blossoms .leaf:nth-child(40) {
left: 30px;
top: 10px;
transform: scale(1.5) rotate(-20deg);
}
.leaves .leaf:nth-child(41),
.cherry-blossoms .leaf:nth-child(41) {
left: 1px;
top: 15px;
transform: scale(0.9) rotate(-5deg);
}
.leaves .leaf:nth-child(42),
.cherry-blossoms .leaf:nth-child(42) {
left: 10px;
top: 18px;
transform: scale(0.5) rotate(5deg);
}
.leaves .leaf:nth-child(44),
.cherry-blossoms .leaf:nth-child(44) {
left: 5px;
top: 35px;
transform: scale(1.5) rotate(-5deg);
}
.leaves .leaf:nth-child(45),
.cherry-blossoms .leaf:nth-child(45) {
z-index: 2;
left: 38px;
top: 35px;
transform: scale(1.8) rotate(70deg);
}
.leaves .leaf:nth-child(46),
.cherry-blossoms .leaf:nth-child(46) {
left: 10px;
top: 5px;
transform: scale(1.3) rotate(70deg);
}
.cherry-blossoms {
background-color: #ffd9df;
border: 0.05em solid black;
box-shadow: none;
}
.cherry-blossoms:before,
.cherry-blossoms:after {
background-color: #ffd9df;
border: 0.05em solid black;
border-top: 0;
box-shadow: none;
}
.cherry-blossoms:after {
border-left: none;
}
.cherry-blossoms .cherry-blossom {
position: absolute;
width: 20px;
height: 20px;
}
.cherry-blossoms .cherry-blossom .petal {
position: absolute;
width: 10px;
height: 10px;
background-color: #ffd9df;
background-image: linear-gradient(-45deg, #ec87bf 10%, #ffd9df 65%);
border-radius: 20% 80%;
box-shadow: -0.04em -0.04em #ec87bf, -0.05em -0.05em black;
}
.cherry-blossoms .cherry-blossom .petal:nth-child(1) {
transform: rotate(40deg);
left: 25%;
top: 2px;
}
.cherry-blossoms .cherry-blossom .petal:nth-child(2) {
transform: rotate(-20deg);
top: 6px;
left: 0;
}
.cherry-blossoms .cherry-blossom .petal:nth-child(3) {
transform: rotate(-90deg);
top: 12px;
left: 2px;
}
.cherry-blossoms .cherry-blossom .petal:nth-child(4) {
transform: rotate(180deg);
top: 12px;
left: 10px;
}
.cherry-blossoms .cherry-blossom .petal:nth-child(5) {
transform: rotate(100deg);
top: 5px;
left: 10px;
}
.cherry-blossoms .cherry-blossom:nth-child(1) {
z-index: 2;
left: 30px;
transform: rotate(10deg) scale(0.8);
top: 70px;
}
.cherry-blossoms .cherry-blossom:nth-child(2) {
z-index: 2;
left: 50px;
transform: rotate(20deg) scale(0.4);
top: 65px;
}
.cherry-blossoms .cherry-blossom:nth-child(3) {
z-index: 2;
left: 45px;
top: 75px;
transform: scale(0.6);
}
.cherry-blossoms .cherry-blossom:nth-child(4) {
z-index: 2;
left: 45px;
top: 50px;
transform: scale(1);
}
.cherry-blossoms .cherry-blossom:nth-child(5) {
z-index: 2;
left: 58px;
top: 70px;
transform: scale(0.5);
}
.cherry-blossoms .cherry-blossom:nth-child(6) {
z-index: 2;
left: 62px;
top: 55px;
transform: scale(0.5);
}
.cherry-blossoms .cherry-blossom:nth-child(7) {
z-index: 2;
left: 55px;
top: 35px;
transform: scale(0.8);
}
.cherry-blossoms .cherry-blossom:nth-child(8) {
z-index: 2;
left: 30px;
top: 55px;
transform: scale(0.5);
}
.cherry-blossoms .cherry-blossom:nth-child(9) {
z-index: 2;
left: 30px;
top: 30px;
transform: scale(1.1);
}
.cherry-blossoms .cherry-blossom:nth-child(10) {
z-index: 2;
left: 15px;
top: 50px;
transform: scale(0.9);
}
.cherry-blossoms .cherry-blossom:nth-child(11) {
z-index: 2;
left: -7px;
top: 40px;
transform: scale(1.1);
}
.cherry-blossoms .cherry-blossom:nth-child(12) {
left: 6px;
top: 60px;
transform: scale(0.8);
}
.cherry-blossoms .cherry-blossom:nth-child(13) {
left: 10px;
top: 75px;
transform: scale(0.5);
}
.cherry-blossoms .cherry-blossom:nth-child(14) {
left: 0;
top: 75px;
transform: scale(0.8);
}
.cherry-blossoms .cherry-blossom:nth-child(15) {
left: -15px;
top: 65px;
transform: scale(0.7);
}
.cherry-blossoms .cherry-blossom:nth-child(16) {
left: -22px;
top: 55px;
transform: scale(0.5);
}
.cherry-blossoms .cherry-blossom:nth-child(17) {
left: 5px;
top: 18px;
transform: scale(1.2) rotate(-40deg);
}
.cherry-blossoms .cherry-blossom:nth-child(18) {
left: 30px;
top: 10px;
transform: scale(1);
}
.cherry-blossoms .cherry-blossom:nth-child(19) {
left: 45px;
top: 18px;
transform: scale(0.5);
}
.cherry-blossoms .cherry-blossom:nth-child(20) {
left: 12px;
top: 38px;
transform: scale(0.5);
}
.cherry-blossoms .cherry-blossom:nth-child(21) {
left: 12px;
top: 0;
transform: scale(0.7) rotate(60deg);
}
.cherry-blossoms .cherry-blossom:nth-child(22) {
left: 25px;
top: -2px;
transform: scale(0.4) rotate(60deg);
}
.cherry-blossoms .cherry-blossom:nth-child(23) {
left: -20px;
top: 45px;
transform: scale(0.4) rotate(60deg);
}
.fruits {
position: absolute;
width: 100px;
height: 100px;
top: -50%;
left: -50%;
z-index: 2;
}
.fruits .pear {
position: absolute;
width: 13px;
height: 15px;
background-color: #ffef94;
border-radius: 100%;
box-shadow: inset 2px 0 #fad500;
border: 0.003em solid black;
}
.fruits .pear:before,
.fruits .pear:after {
content: '';
position: absolute;
}
.fruits .pear:before {
width: 20px;
height: 20px;
background-color: #ffef94;
border-radius: 100%;
top: 6px;
border-right: 0.003em solid black;
box-shadow: inset 2px -2px #fad500, -0.06em 0.08em black;
}
.fruits .pear:after {
width: 5px;
height: 8px;
border-left: 2px solid #641b1b;
border-radius: 100%;
top: -7px;
left: 3px;
}
}
公共方法
页面路径:/utils/util.js
```js /* * @description 公共方法 /
// 获取用户信息 const getUserInfo = () => { let userInfo = localStorage.getItem('userInfo'); if (userInfo) { return JSON.parse(userInfo); } return null; };
// 保存用户信息 const saveUserInfo = userInfo => { if (userInfo) { localStorage.setItem('userInfo', JSON.stringify(userInfo)); } };
/* * 两个是否可以整除 * @param {number} num1 除数 * @param {number} num2 被除数 * @return {boolean} 是否整除的布尔值 / const getNumDivisibleFlag = (num1, num2) => { let flag = false; // 如果除数小于被除数 则表示不可以被整除 if (num1 > num2 && num1 / num2 > 1) { flag = true; } return flag; };
export default { getUserInfo, saveUserInfo, getNumDivisibleFlag }; ```
最终UI
端午活动
活动规则
活动时间
1.2022-5-31 至 2022-6-5,提前预热3天。
2.页面上设置活动倒计时
- 活动结束前,展示距离活动结束还剩多长时间,时间格式为DD天 hh:mm:ss;
- 活动结束后,展示内容为"活动已结束";
兑换规则
食材兑换比例
| 粽子类型 | 需要材料 | | ---- | ------------------ | | 糯米粽子 | 10 * 糯米 + 2 * 粽叶 + 2 * 红枣 |
食材兑换规则
- 通过页面按钮进行兑换,当食材数量不足时,按钮不可点击,当食材数量充足时可以进行点击。
- 点击兑换按钮唤起兑换弹窗,可以通过加减号进行兑换数量的修改,当达到最大可兑换值时,加号不可点击。
- 确定兑换之后,粽子数量增加,食材数量对应减少。
功能实现
活动页面
文件路径:/festival/index.jsx
```js /* * @description 端午活动 / import React, { useEffect, useState } from 'react'; import classnames from 'classnames'; import moment from 'moment'; import Back from '@/components/Back'; import { Modal, Stepper } from 'antd-mobile'; import { QuestionCircleFill } from 'antd-mobile-icons'; import util from '../../utils/util'; import './index.less';
const Festival = () => { const userInfo = util.getUserInfo() || {}; const [festivalObj, setFestivalObj] = useState( userInfo.festival ? userInfo.festival : { nuomi: 20, zongye: 10, hongzao: 10, zongzi: 150, }, ); const list = [ { key: 'nuomi', name: '糯米', }, { key: 'zongye', name: '粽叶', }, { key: 'hongzao', name: '红枣', }, { key: 'zongzi', name: '粽子', }, ];
// 是否可以进行兑换操作的布尔值 true-能 false-不能 const [activeFlag, setActiveFlag] = useState(false);
const [visible, setVisible] = useState(false); const [count, setCount] = useState(0); // 兑换的粽子数量 const [convertNum, setConvertNum] = useState(1); const [countdown, setCountdown] = useState(''); let timer = null;
// 获取当前兑换按钮是否可以点击 const getInactiveFlag = festivalObj => { let activeInit = false; let nuomi = festivalObj.nuomi; let zongye = festivalObj.zongye; let hongzao = festivalObj.hongzao; if (nuomi && zongye && hongzao) { let nuomiFlag = util.getNumDivisibleFlag(nuomi, 10); let zongyeFlag = util.getNumDivisibleFlag(zongye, 2); let hongzaoFlag = util.getNumDivisibleFlag(hongzao, 2); if (nuomiFlag && zongyeFlag && hongzaoFlag) { activeInit = true; } } setActiveFlag(activeInit); };
const getCountdown = () => { let nowDate = new Date(); // console.log(nowDate, 'nowDate'); // 获取的2022-06-05的23:59:59的时间戳 let endTime = moment('2022-06-05').endOf('day').format('x');
let countdownInit = '';
// 剩余时间 毫秒
let surplusTime = endTime - nowDate.getTime();
if (surplusTime <= 0) {
clearTimeout(timer);
countdownInit = '活动已结束';
setCountdown(countdownInit);
} else {
// 剩余时间 秒
let runTime = surplusTime / 1000;
const day = Math.floor(runTime / 86400);
runTime = runTime % 86400;
const hour = Math.floor(runTime / 3600);
runTime = runTime % 3600;
const minute = Math.floor(runTime / 60);
runTime = runTime % 60;
const second = Math.floor(runTime);
const dayText = day ? `${day}天` : '';
countdownInit = `剩余时间:${dayText} ${hour}:${minute}:${second}`;
setCountdown(countdownInit);
timer = setTimeout(getCountdown, 1000);
}
};
useEffect(() => { getInactiveFlag(festivalObj); getCountdown(); }, []);
useEffect(() => { // 清除定时 return () => { clearInterval(timer); }; }, []);
// 顶部提示 const headTip = () => { return Modal.show({ title: '"粽"得凤仪', content: (
10糯米+2粽叶+2*红枣可以兑换1个糯米粽子。
当糯米、粽叶、红枣的比例不是5:1:1时,无法进行兑换。
当前粽子数量达到50个可获得称号“淑仪倾城”。
当前粽子数量达到100个可获得称号“花容初绽”。
当前粽子数量达到200个可获得称号“花成蜜就”。
当前粽子数量达到300个可获得称号“宠冠六宫”。
当前粽子数量达到400个可获得称号“凤仪千载”。
称号自动获取无需额外操作
// 粽子展示 const zongziContent = () => { return (
// 兑换确定操作 const convertOnConfirm = () => { setVisible(false); let festivalObjInit = { ...festivalObj }; console.log(convertNum, 'convertNum'); festivalObjInit.nuomi -= convertNum * 10; festivalObjInit.zongye -= convertNum * 2; festivalObjInit.hongzao -= convertNum * 2; festivalObjInit.zongzi += convertNum; console.log(festivalObjInit, 'festivalObjInit'); // 设置缓存 let userInfoInit = { ...userInfo }; userInfoInit.festival = festivalObjInit; util.saveUserInfo(userInfoInit); setFestivalObj(festivalObjInit); getInactiveFlag(festivalObjInit); };
// 获取可以兑换的数量 const getConvertCount = () => { let nuomi = festivalObj.nuomi; let zongye = festivalObj.zongye; let hongzao = festivalObj.hongzao; let nuomiNum = Math.floor((nuomi * 100) / (10 * 100)); let zongyeNum = Math.floor((zongye * 100) / (2 * 100)); let hongzaoNum = Math.floor((hongzao * 100) / (2 * 100)); return Math.min(nuomiNum, zongyeNum, hongzaoNum); };
// 兑换操作 const handleConvert = () => { if (!activeFlag) return; const count = getConvertCount(); setConvertNum(1); setCount(count); setVisible(true); };
return (
export default Festival; ```
样式:/festival/index.less
```js .festival { width: 100%; max-width: 100%; height: 100vh; background: #46272d; position: relative; overflow: hidden; &-content { position: absolute; top: 0; left: 0; z-index: 999; width: 100%; height: 100%; } &-room { width: 100%; position: absolute; top: 0; left: 0; z-index: 99; &-wall { width: 100%; height: 450px; background: #e8dfe0; position: relative; .wall-poetry { position: absolute; top: 180px; left: 100px; width: 170px; height: 230px; background: #c1a98f; &-nail { width: 14px; height: 14px; position: absolute; top: -60px; left: 50%; border: 2px solid #ffedbb; border-radius: 50%; background: #b5a9a9; margin-left: -7px; &::before { content: ''; position: absolute; top: 27px; left: 0; width: 90px; height: 3px; border-radius: 3px; background: #b5a9a9; transform: rotate(30deg); z-index: 20; } &::after { content: ''; position: absolute; top: 27px; right: 0; width: 90px; height: 3px; border-radius: 3px; background: #b5a9a9; transform: rotate(-30deg); z-index: 20; } }
&-shaft {
position: absolute;
left: -18px;
height: 8px;
width: 120%;
background: #a97e78;
border-radius: 8px;
z-index: 90;
&-top {
top: -8px;
}
&-bottom {
bottom: -8px;
}
&::before {
content: '';
position: absolute;
top: 0;
left: 15px;
height: 100%;
width: 10px;
background: #c8b044;
}
&::after {
content: '';
position: absolute;
top: 0;
right: 15px;
height: 100%;
width: 10px;
background: #c8b044;
}
}
&-inner {
position: absolute;
top: 20px;
left: 20px;
width: 130px;
height: 190px;
background: #ece8e5;
font-size: 13px;
font-weight: 300;
color: #333;
padding: 15px 13px;
z-index: 99;
}
&-title {
font-size: 14px;
line-height: 1.5;
margin-bottom: 8px;
}
&-author {
line-height: 1.5;
margin-bottom: 5px;
}
}
}
&-floor {
width: 100%;
height: 250px;
position: relative;
background: #946962;
overflow: hidden;
.floor {
&-line {
width: 1px;
height: 100%;
background: linear-gradient( to bottom, #b48e5e 20%, #eebe88 40%, #fce49c 60%, #9f725a 80%, #f7c887 100%);
position: absolute;
top: 0;
}
&-line1 {
left: 0;
transform: rotate(10deg);
}
&-line2 {
left: 10%;
transform: rotate(10deg);
}
&-line3 {
left: 23%;
transform: rotate(5deg);
}
&-line4 {
left: 34%;
transform: rotate(2deg);
}
&-line5 {
left: 45%;
}
&-line6 {
right: 43%;
transform: rotate(-2deg);
}
&-line7 {
right: 32%;
transform: rotate(-5deg);
}
&-line8 {
right: 20%;
transform: rotate(-8deg);
}
&-line9 {
right: 10%;
transform: rotate(-10deg);
}
&-line10 {
right: 0;
transform: rotate(-10deg);
}
}
}
} &-head { width: 100%; height: 50px; display: flex; justify-content: flex-start; align-items: center; margin-top: 30px; &-tip { margin-left: 80px; margin-right: 30px; } &-title { color: #67b898; line-height: 50px; font-size: 28px; text-align: center; font-weight: 500; } } &-time { line-height: 30px; width: 60%; text-align: center; margin: 5px auto; font-size: 13px; color: #e45453; background: #fff; border-radius: 30px; } &-modal { width: 100%; padding: 0 5px; &-title { position: relative; height: 22px; line-height: 22px; text-align: center; margin-bottom: 10px; color: #af8368; &::before { content: ''; width: 40px; height: 2px; background: #eec2c1; position: absolute; top: 10px; left: 15px; border-radius: 2px; } &::after { content: ''; width: 40px; height: 2px; background: #eec2c1; position: absolute; top: 10px; right: 15px; border-radius: 2px; } } p { line-height: 1.4; font-weight: 300; font-size: 13px; position: relative; padding-left: 10px; &::before { content: ''; width: 4px; height: 4px; background: #af8368; border-radius: 50%; position: absolute; top: 6px; left: -2px;
}
}
&-text {
line-height: 20px;
font-size: 15px;
color: #cd9769;
margin: 20px auto 15px;
text-align: center;
}
&-stepper {
margin: 0 auto;
width: 40%;
}
&-confirm {
width: 120px;
height: 30px;
line-height: 30px;
background: #d5834b;
color: #fff;
font-size: 15px;
font-weight: 300;
border-radius: 30px;
margin: 20px auto 10px;
text-align: center;
}
} &-convert { width: 90%; height: 270px; position: absolute; top: 250px; left: 5%; z-index: 999; background: linear-gradient(to top right,rgba(148,215,102,0.9) 0,rgba(67,171,174,0.9) 40%); box-shadow: 1px 1px 30px #8fde5f; border-radius: 10%; &-zongzi { position: absolute; bottom: -14px; right: -8px; transform: scale(0.8); z-index: 1000; } &-zongzi2 { position: absolute; bottom: -20px; right: 15px; transform: scale(0.6) rotate(20deg); z-index: 1001; } &-num { width: 90%; height: 30px; margin-left: 5%; margin-top: 40px; display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #ffdaa3; padding-bottom: 10px; &-item { font-size: 16px; color: #ffdaa3; font-weight: 500; } } &-rule { display: flex; justify-content: space-between; align-items: center; margin-top: 30px; font-size: 20px; color: #fff; width: 80%; margin-left: 10%; &-nuomi { width: 30px; height: 46px; background: #fff; border-radius: 50%; position: relative; box-shadow: inset 6px -1px 1px 0 #efefe5; transform: rotate(20deg); } &-zongye { width: 24px; height: 50px; background: #080; border-radius: 300%; transform: rotate(30deg) skewY(50deg); } &-hongzao { width: 30px; height: 46px; background: #f65662; border-radius: 40%; position: relative; box-shadow: inset -1px -1px 1px 0 #fff; transform: rotate(20deg); &::before { content: ''; position: absolute; top: -1px; left: 10px; width: 10px; height: 3px; background: #a84e43; border-radius: 50%; } } } &-btn { width: 60px; height: 60px; border: 2px solid #fff8db; background: #f295a7; color: #fff; font-size: 16px; border-radius: 50%; margin: 30px auto 0; display: flex; justify-content: center; align-items: center; &.inactive { background: #afa9e7; } } } &-zongzi { position: relative; width: 50px; height: 60px; z-index: 990; overflow: hidden; border-radius: 0 0 35% 35%; &-left { width: 60px; height: 40px; background: #73bd5c; border-radius: 30% 50% 0 0; position: absolute; bottom: -22px; left: -22px; z-index: 993; transform: rotate(47deg); } &-center { width: 50px; height: 70px; background: #fff; border-radius: 50% 70% 0 0 ; position: absolute; top: 6px; left: 0; z-index: 991; box-shadow: inset -6px -1px 1px 0 #efefe5; } &-right { width: 60px; height: 40px; background: #73bd5c; border-radius: 0 30% 0 0; position: absolute; bottom: -17px; right: -19px; z-index: 993; transform: rotate(-47deg); } } } ```
最终UI
活动展示
兑换弹窗展示
总结
本次收获还是挺多的。
- CSS用的别以前熟练了很多,这次的游戏里除了头像图片、一颗树、一簇花,其他的都是我用CSS写出来的,没有用图片素材,实现过程不断收获新的创意。说起来多亏这段时间码上掘金活动,我才能使用CSS实现功能做的如此之快,ღ( ´・ᴗ・` );
- 游戏设计,体验了一把产品/策划的感觉,站在不同的角度去思考需要实现的功能,锻炼逻辑思维,很有收获;
- 核心功能的实现,包括内务收集的计算、食材的随机掉落计算、粽子兑换的计算等多个计算功能,虽然方法可能不是最优,但是在遇到类似的功能实现算是有经验了;
还差一个github的地址,等有时间我把所有代码上传后,补充一下github地址。
参考文章
- await-to-js 源码分析,体验一把捕获异常的优雅
- CSS伪类的第三集,原来伪类也可组CP
- 从:is()说起,开启CSS伪类第二集
- 一组纯CSS开发的聊天背景图,帮助避免发错消息的尴尬
- 「CSS特效」我的发呆专属,反复解锁手机屏幕
- 「技术分享」以Antd为例,快速打通UI组件开发的任督二脉
- 「功能实现」我封装了一个表单组件,感觉离财富自由又近了一步
- 「经验总结」高效开发,老代码可以这样动
- 前端开发提效小技巧之业务功能篇
- 人生有忙忙碌碌,也有诗和远方 | 2022年中总结
- 【端午节】新奇体验,我用react实现网页游戏的全过程(包括规则设计)
- 【暑假记忆】消暑神器,我用CSS复刻了一个游戏机
- 突围?我愿称之为向上的攀登者
- 【孟夏之遇】望孟夏之短夜兮,萤星相伴
- 【技术学习】SVG-边学边做
- 【TS实践】自己动手丰衣足食的TS项目开发
- 【码上掘金】通过FileReader读取Excel文件内容
- 【Taro开发】四月芳菲,Taro观赏指南
- 【Node.js】青梅煮酒,聊聊zlib压缩
- 【知识点】关于iframe跨域通信