根据 Remix 完成一个简略的点赞 ❤️作用

一、简介

在 ToC 的项目中,点赞功用是一个常用的的功用,在干流视频网站中,点赞作用做的十分优异。

二、挑选技术与作用完成

  • 根据 Remix 的 React 结构
  • css module 编写款式
  • 仅仅是前端完成,没有对接后端接口
  • 移动端点赞作用
  • PC 端的点赞作用

三、创立项目安装依靠

npx create-remix app
pnpm add uuid

四、款式重置

  • global.css
* {
  padding: 0;
  margin: 0;
}

root 中引进重置款式

import css from '~/styles/global.css'
export const links: LinksFunction = () => [
  { rel: "stylesheet", href: css },
  ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
];

五、辅助函数

5.1) 辅助函数:emoji 随机生成

export function getRandomEmoji() {
  // 随机挑选一个起始点(Unicode 规模中的值)
  const start = 0x1F600; // 起始点
  const end = 0x1F64F; // 完毕点
  // 生成随机的 Unicode 编码
  const randomCodePoint = Math.floor(Math.random() * (end - start   1))   start;
  // 将 Unicode 编码转换为对应的 emoji 并返回
  return String.fromCodePoint(randomCodePoint);
}

从 emoji 的Unicode 规模中获取一个随机的 emoji 替代抖音中图画。

5.2) 辅助函数:特定时间内记载次数

在特点时间(好比 300ms)内履行记载一次,调用一次加 1,如果 300 ms 内,超越 10 次,输出 x10。当最有一次点击超越 300ms 后,没有从头点击,次数归 0。

export function huntCount(instance: MangerHunterCount, time: number) {
  let timer: any;
  return function () {
    if (timer) {
      clearTimeout(timer);
      timer = setTimeout(() => {
        console.log("reset");
        instance.reset();
        timer = null;
      }, time);
    } else {
      timer = setTimeout(() => {
        console.log("reset");
        instance.reset();
        timer = null;
      }, time);
    }
    instance.addOne();
  };
}
export class MangerHunterCount {
  count: number;
  constructor() {
    this.count = 0;
  }
  addOne() {
    this.count  = 1;
  }
  reset() {
    this.count = 0;
  }
}

六、移动端

根据 Remix 完成一个简略的点赞 ❤️作用

6.1) 逻辑

  • 移动端在点击屏幕位置有 pop 弹出作用,一起又连击数字作用(连击超越 10 次)。
  • 移动端点击屏幕,点赞作用从右下角底部接连点赞接连 pop 出来,然后躲藏。

6.2) TSX 完成

import type { MetaFunction } from "@remix-run/node";
import { useState } from "react";
import styles from "~/styles/index.module.css";
import * as uuid from "uuid";
import { getRandomEmoji } from "~/utils/index";
export const meta: MetaFunction = () => {
  return [
    { title: "New Remix App" },
    { name: "description", content: "Welcome to Remix!" },
  ];
};
export default function Index() {
  const [list, setList] = useState<any>([]);
  return (
    <div className={styles.page}>
      <h1>Welcome to Remix</h1>
      <div className={styles.wrap}>
        <div className={styles.listWrap}>
          {list.map((li: any) => {
            return (
              <div
                onAnimationEnd={() => {
                  setList((pre: any) => pre.filter((l: any) => l.id !== li.id));
                }}
                key={li.id}
                className={styles.emoji}
              >
                {li.content}
              </div>
            );
          })}
        </div>
        <div
          className={styles.heart}
          onClick={() => {
            setList([
              ...list,
              { id: uuid.v4(), content: getRandomEmoji() || "⛄" },
            ]);
          }}
        >
          ❤️
        </div>
      </div>
    </div>
  );
}
  • list 列表维护点赞的 pop。
  • ❤️ 点击之后,运用 uuid 增加随机的 emoji 的图画。
  • 当 pop 动画完毕之后,更具 uuid 过滤对应的值, 直到列表清空。

6.3) CSS

.page {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  height: 90vh;
  cursor: pointer;
  background-color: antiquewhite;
}
.wrap {
  position: relative;
  display: flex;
  margin-top: 300px;
}
.listWrap {
  position: relative;
}
.heart {
  position: relative;
  top: 0px;
  display: flex;
  cursor: pointer;
  font-size: 30px;
}
.heart:active {
  position: relative;
  top: 0px;
  display: flex;
  cursor: pointer;
  font-size: 30px;
  transform: scale(1.2);
}
.emoji {
  position: absolute;
  animation-duration: 3s;
  animation-name: pop;
  animation-fill-mode: forwards;
  cursor: pointer;
  font-size: 30px;
}
@keyframes pop {
  from {
    transform: translateY(-30px) scale(1);
    opacity: 0.6;
  }
  50% {
    transform: translateY(-200px) scale(2);
    opacity: 1;
  }
  to {
    transform: translateY(-300px) scale(1);
    opacity: 0;
  }
}

css 中定义了 pop 动画,这个动画在完毕时运用 forwards 特点操控在最终一帧。

七、PC 端

7.1) 逻辑

  • 没有连击作用
  • PC 端与移动的作用不一样,pop 的呈现位置时 PC 端点击时,根据点击位置确定的
  • Pop 作用是进入变大,突然变小,然后渐进式变大(一起透明度变低,然后消失)
  • PC 端在接连点击 10 次以上,会显示 x10 字样提示
  • 接连点击监听 dbclick 不在合理,应该自己模拟 dbclick 点击事情

根据 Remix 完成一个简略的点赞 ❤️作用

7.2) TSX

import type { MetaFunction } from "@remix-run/node";
import { useEffect, useState } from "react";
import styles from "~/styles/client.pc.module.css";
import * as uuid from "uuid";
import { getRandomEmoji } from "~/utils/index";
import { MangerHunterCount, huntCount } from "~/utils/hunt-count";
const inst = new MangerHunterCount();
const fn = huntCount(inst, 2000);
let clicks = 0;
let timer: any = null;
export const meta: MetaFunction = () => {
  return [
    { title: "New Remix App" },
    { name: "description", content: "Welcome to Remix!" },
  ];
};
export default function Index() {
  const [list, setList] = useState<any>([]);
  function handleClick(e: any)  {
    const { clientX, clientY } = e;
    clicks  ;
  if (clicks === 1) {
    timer = setTimeout(function() {
      // 单击事情
      clicks = 0;
      // 在这里履行点赞操作
      console.log("单击点赞");
    }, 300); // 设置延迟时间,这里是300毫秒
  } else {
    clearTimeout(timer);
    // 双击事情
    clicks = 0;
    fn();
    setList((pre: any) => {
      return [
        ...pre,
        {
          id: uuid.v4(),
          content: getRandomEmoji(),
          x: clientX - 10,
          y: clientY - 10,
          count: inst.count,
        },
      ];
    });
  }
  }
  useEffect(() => {
    window.addEventListener("click", handleClick);
    // eslint-disable-next-line react-hooks/exhaustive-deps
    return () => {
      window.removeEventListener("click", handleClick)
    }
  }, []);
  return (
    <div className={styles.page}>
      {/* <h1>Welcome to Remix</h1> */}
      <div className={styles.wrap}>
        <div className={styles.listWrap}>
          {list.map((li: any) => {
            return (
              <div
                onAnimationEnd={() => {
                  setList((pre: any) => pre.filter((l: any) => l.id !== li.id));
                }}
                key={li.id}
                style={{
                  top: li.y,
                  left: li.x,
                }}
                className={styles.emoji}
              >
                {li.content}
                <span className={styles.count}>
                  {li.count >= 10 ? "x"   li.count : null}
                </span>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

7.3) css

.page {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  height: 90vh;
  cursor: pointer;
  background-color: antiquewhite;
}
.wrap {
  position: relative;
  display: flex;
  width: 100%;
  height: 500px;
}
.listWrap {
  position: relative;
  display: flex;
  background-color: aqua;
}
.emoji {
  position: absolute;
  animation-name: popnew;
  animation-duration: 0.9s;
  animation-fill-mode: forwards;
  animation-timing-function: ease-in-out;
  cursor: pointer;
  font-size: 30px;
  user-select: none;
}
@keyframes popnew {
  from {
    transform: scale(2);
    opacity: 0.08;
  }
  15% {
    transform: scale(1);
    opacity: 1;
  }
  80% {
    transform: translateY(-20px) scale(1) rotate(30deg);
    opacity: 1;
  }
  to {
    transform:  translateY(-100px) scale(5);
    opacity: 0;
  }
}
.count {
  position: absolute;
  top: -20px;
  color: #fff;
  font-size: 18px;
}

八、小结

本文首要完成根据 React 的前端点赞作用,涉及不少的知识点, css 动画与过渡,操控 css 动画的行为,emoji 随机生成。本文是对于点赞基本探究与完成,期望对读者有所协助。