【端午節】新奇體驗,我用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跨域通訊