Skip to content

RedrockTeam/turn-over-great-deeds

 
 

Repository files navigation

整体结构

为了方便只展示 src 目录下的文件

├─assets            // 静态资源文件存放图片和字体文件
│  ├─font				// 字体文件 https://www.lcddjm.com/font 提取所需要的字
│  ├─image				// 图片 https://tinypng.com 压缩
|  ...
├─component       	// 每个页面所需要的组件
│  ├─Base*** 			// 基础组件
│  ├─Index*** 			// 首页所需的组件
│  └─Section*** 		// 关卡页面所需的组件     
|  ...
├─interface         // ts 所用的接口
├─pages             // 页面文件
│  ├─Choose				// 选择关卡
│  ├─Index				// 首页
│  ├─RankList			// 排行榜
│  ├─Section			// 每个关卡
│  ├─Success			// 挑战成功
└─utils              // 工具函数
    ├─convertFloatToInt	// 将浮点数转换成 10 进制数
    ├─convertNumberToUppercase // 将阿拉伯数字转换成中文数字(只个位)
    ├─convertPxToVw		// 将 px 转换成 vw (只用于牌数据,其他使用库来做)
    ├─sortCard			// 打乱牌顺序
    ├─useFetch			// 将所有请求包装成 custom hooks
    ├─useInterval		// 将 setInterval 包装成 custom hooks
    └─useTimeout		// 将 setTimeout 包装成 custom hooks
    

技术栈

  • React
  • react-router-dom // 路由
  • use-react-router // 通过 hooks 的方式获取到 react-router 的 context
  • react-spring // 动画
  • styled-components // CSS-in-JS
  • styled-normalize // 重置默认 CSS 样式
  • styled-px2vw // px to vw
  • react-id-swiper // 让 swiper 更适合 react
  • swiper // 触摸内容滑动 js 插件

项目运行

使用 Create React App 创建,按照标准即可完成

环境变量文件 根目录 .env

REACT_APP_BE_URL=https://***
REACT_APP_GA=UA-****-*
REACT_APP_NAME=**
REACT_APP_DESC=****
REACT_APP_ICO=https://***/favicon.ico
REACT_APP_FE_URL=https://***/#/

注意点

  1. 移动端 click 300 ms 延迟

    因为本项目对于点击响应速度有要求,但是使用 touch** 的方式会导致点击穿透,故使用

    <meta name="viewport" content="user-scalable=no">
    <meta name="viewport" content="initial-scale=1,maximum-scale=1">

    禁止放大缩小

  2. 点击会有高亮

    覆盖 webkit/safari, Blink/Chrome 几乎所有的移动浏览器

    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);

特别的一些地方

  • 使用 hooks 拿到 router 的 context

      const {
        match: { params },
      } = useRouter<ISectionRouterProp>();
      const { step } = params;
      const [stepNum] = useState(Number(step));
  • 使用自定义 hooks 处理数据

    • initialData 从后端拿到的数据的大体格式,这样就不需要自己写 interface 了,还解决了,没有拿到数据然后渲染出错的问题
    • postData 因为本项目只有 get 和 post 所以直接以是否有该来进行判断
    // src/utils/useFetch.ts
    function useFetch<T>(initialUrl: string, initialData: T, postData?: any) {
      const [data, setData] = useState(initialData);
      const [url, setUrl] = useState(initialUrl);
      const [isLoading, setIsLoading] = useState(false);
      const [isError, setIsError] = useState(false);
      const didCancel = useRef<boolean>();
      useEffect(() => {
        didCancel.current = false;
        const fetchData = async () => {
          setIsError(false);
          setIsLoading(true);
          try {
            let body;
            if (postData) {
              const formBody = [];
              for (const property in postData) {
                const encodedKey = encodeURIComponent(property);
                const encodedValue = encodeURIComponent(postData[property]);
                formBody.push(`${encodedKey}=${encodedValue}`);
              }
              body = formBody.join('&');
            }
            const result = await fetch(`${process.env.REACT_APP_BE_URL}${url}`, {
              method: postData ? 'POST' : 'GET',
              body: postData ? body : null,
              headers: {
                'content-type': 'application/x-www-form-urlencoded',
                token: localStorage.getItem('id_token') || '',
              },
            });
    
            if (!didCancel.current) {
              result.json().then(a => setData(a));
            }
          } catch (error) {
            if (!didCancel.current) {
              setIsError(true);
            }
          }
    
          setIsLoading(false);
        };
        fetchData();
    
        return () => {
          didCancel.current = true;
        };
      }, [url]);
    
      const doFetch = (_url: string) => {
        setUrl(_url);
      };
    
      return {
        data,
        isLoading,
        isError,
        doFetch,
      };
    }
  • 翻牌逻辑

    • 将牌的大小数据以及两牌之间的距离记录到一个数组中,以方便修改以及调用

      // src/data/card.ts
      const cardsSize = convertPxToVw([
        {
          width: 282,
          height: 359,
          marginRight: 17,
          marginBottom: 18,
          imageHeight: 232,
          imageWidth: 245,
          fontSize: 24,
        },
        ...
      ]);
    // src/component/SectionCard.tsx
    ...
      // 初始化牌的数据 
      const [cardData, setCardData] = useState(
        sortCard(cardDatas[stepNum - 1]).map((item, index) => ({
          ...item,
          isShow: false, // 是否翻开
          isHide: false, // 是否已经成功比对消失了
          id: index,
        })),
      );
      // 如上将牌的大小信息拿到
      const [cardSize] = useState(cardsSize[stepNum - 1]);
      const handelCardClick = (index: number) => {
        if (cardData[index].isHide) return; // 如果隐藏了就不执行了
        // 先查询上一次的 -> 是否已经有两张已经翻开了
        const temp = cardData.filter(item => item.isShow && !item.isHide);
        // 如果有两张已经翻开 不能新翻开牌
        if (temp.length !== 2 || cardData[index].isShow) {
          // 将牌的状态调转
          cardData[index].isShow = !cardData[index].isShow;
          // 设定新的 state
          setCardData([...cardData]);
          // 查找调整过后翻开的的牌
          const isShowCard = cardData.filter(item => item.isShow && !item.isHide);
          // 如果当前是新翻开的牌的情况 并且牌的数量为 2 检查是否满足消灭的条件
          if (cardData[index].isShow && isShowCard.length === 2) {
            if (isShowCard[0].content === isShowCard[1].content) {
              // 将牌的状态调转
              cardData[isShowCard[0].id].isHide = true;
              cardData[isShowCard[1].id].isHide = true;
              // 设定新的 state
              setCardData([...cardData]);
              // 在这种情况下查看是否还有牌没有隐藏
              // 如果没有那么就触发时间暂停
              if (!cardData.some(item => !item.isHide)) {
                const cardFinished = new Event('cardFinished');
                window.dispatchEvent(cardFinished);
                setShowMask(true);
              }
            } else {
              // 如果不满足条件,自己翻回去
              setTimeoutRef.current = setTimeout(() => {
                cardData[isShowCard[0].id].isShow = false;
                cardData[isShowCard[1].id].isShow = false;
                setCardData([...cardData]);
              }, 500);
            }
          }
        }
      };
    ...
  • 组件间传递信息

    因为比较简单没有引入 redux 直接使用原生的自定义事件,并将数据存储在 localstorage 中

  • 成功页面的彩带飞落

    参考 https://github.com/cahilfoley/react-snowfall 做了些修改

    • 使用 http://demo.qunee.com/svg2canvas/ (canvg.js) 将 svg 转换成 bezier Curve

      // src/snowfall/hooks.ts
         	ctx.moveTo(-0.012, 15.45);
          ctx.bezierCurveTo(-0.012, 15.45, -0.196, 25.764, 7.897, 24.97);
          ctx.bezierCurveTo(7.3, 25.054, 13.975, 9.882, 25.979, 3.028);
          ctx.bezierCurveTo(24.911, 2.685, 20.704, 5.048, 20.859, 0.802);
          ctx.bezierCurveTo(21.013, -3.443, 4.371, 9.818, -0.012, 15.45);
          ctx.closePath();
    • 然后使用 drawImage 将生成的 canvas 放到主要的 canvas 里

      ctx.drawImage(this.image, this.params.x, this.params.y);

    更新日志

    本项目遵从 Angular Style Commit Message Conventions,更新日志由 standard-changelog 自动生成。完整日志请点击 CHANGELOG.md

图例

1569659314915

1569659472763

1569659483498

1569659514188

1569659525060

1569659533629

1570190594142

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 95.2%
  • HTML 4.1%
  • CSS 0.7%