Skip to content

Commit

Permalink
feat: 实现纯css的打字效果,解决性能问题
Browse files Browse the repository at this point in the history
  • Loading branch information
SSmJaE committed Mar 19, 2023
1 parent e90f86e commit e00020c
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 146 deletions.
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"react-draggable": "^4.4.5",
"react-error-boundary": "^3.1.4",
"simplebar-react": "^3.2.1",
"typeit": "^8.7.1",
"valtio": "^1.10.3"
},
"devDependencies": {
Expand All @@ -35,7 +34,6 @@
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react": "^3.1.0",
"dotenv": "^16.0.3",
"install": "^0.13.0",
"rollup-plugin-visualizer": "^5.9.0",
"typescript": "^4.9.5",
"vite": "^4.1.4",
Expand Down
18 changes: 0 additions & 18 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/views/Log/records/Info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export function InfoRecord({ record }: { record: IInfoRecord }) {
>
提示
</InlineTag>

<InfoRecordContainer
style={{
...spring,
Expand Down
33 changes: 7 additions & 26 deletions src/views/Log/records/Question.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { useHover, useMount } from "ahooks";
import { useHover } from "ahooks";
import { createRef, useRef, useState } from "react";
import { ErrorBoundary } from "react-error-boundary";

import { store } from "@/src/store";
import logger, { IQuestionContent, IQuestionRecord } from "@/src/utils/logger";
import { useTheme } from "@emotion/react";
import { animated, config, useSpring, useSprings, useTrail } from "@react-spring/web";

import { theme } from "../../App";
import Button from "../../components/Button";
import { InlineTag, useSlideIn } from "../../components/InlineTag";
// import TypeIt from "typeit-react";
import TypeIt from "../../components/TypeIt";
import { TypingAnimation } from "../../components/TypingAnimation";

function SolveButton({ content: { solve, answerText } }: { content: IQuestionContent }) {
const [isHovering, setIsHovering] = useState(false);
Expand Down Expand Up @@ -94,8 +91,6 @@ export function QuestionRecord({ record }: { record: IQuestionRecord }) {

const spring = useSlideIn();

// TODO 尝试函数式风格声明typedit,避免销毁时报错无法捕获

return (
<animated.div
onMouseEnter={() => {
Expand Down Expand Up @@ -139,25 +134,11 @@ export function QuestionRecord({ record }: { record: IQuestionRecord }) {
{/* 不考虑虚拟列表的话,没必要conditional 选择typeit还是普通span */}

{store.userSettings.enableTyping ? (
<TypeIt
options={{
startDelay: 600,
// waitUntilVisible: true,
cursorChar: "█",
speed: 25,
lifeLike: true,
afterComplete: (instance: any) => {
try {
instance?.destroy();
} catch (error) {
logger.debug("typeit destroy error");
}
},
}}
style={{}}
>
{record.content.answerText}
</TypeIt>
<TypingAnimation
content={record.content.answerText}
startDelay={600}
interval={35}
/>
) : (
<animated.span
style={{
Expand Down
100 changes: 0 additions & 100 deletions src/views/components/TypeIt.tsx

This file was deleted.

119 changes: 119 additions & 0 deletions src/views/components/TypingAnimation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import styled from "@emotion/styled";
import { useMount } from "ahooks";
import { useState } from "react";

// /**
// * TypeIt的打字效果总是卡顿,自己实现
// *
// * 而且build之后,不知道为啥白屏
// *
// * */
// function TypingAnimation({
// content,
// startDelay = 600,
// cursorChar = "█",
// speed = 25,
// }: {
// content: string;
// startDelay: number;
// cursorChar: string;
// speed: number;
// }) {
// const [displayedText, setDisplayedText] = useState("");

// useMount(() => {
// let charIndex = 0;

// setTimeout(() => {
// const interval = setInterval(() => {
// if (charIndex < content.length) {
// const text = content.slice(0, charIndex + 1) + cursorChar;
// setDisplayedText(text);

// charIndex++;
// } else {
// clearInterval(interval);
// setDisplayedText(content);
// }
// }, speed);
// }, startDelay);
// });

// return <>{displayedText}</>;
// }

const TypingAnimationContainer = styled.span<{
count: number;
duration: number;
showCursor: boolean;
}>`
line-height: normal;
/* font-family: monospace; */
/* 有中文的话,尤其是中英文混杂的情况,用不用monospace都一样了 */
background: linear-gradient(#000000 0 0) 0 0
${(props) =>
props.showCursor
? ", linear-gradient(-90deg, #000000 10px, #0000 0) 10px 0"
: undefined};
background-clip: text ${(props) => (props.showCursor ? ", padding-box" : undefined)};
color: transparent;
background-size: calc(${(props) => props.count} * 1ch) 200%;
background-repeat: no-repeat;
@keyframes typing {
from {
background-size: 0 200%;
}
}
@keyframes cursor {
50% {
background-position: 0 -100%;
}
}
animation: typing ${(props) => props.duration}ms linear 1 alternate;
animation-timing-function: steps(${(props) => props.count});
`;

/** 还是纯css实现吧,js的实现,都很卡 */
export function TypingAnimation({
content,
startDelay = 600,
interval = 25,
}: {
content: string;
startDelay: number;
interval: number;
}) {
const [shouldDisplay, setShouldDisplay] = useState(false);
const [showCursor, setShowCursor] = useState(false);

const duration = content.length * interval;

useMount(() => {
setTimeout(() => {
setShouldDisplay(true);
setShowCursor(true);

setTimeout(() => {
setShowCursor(false);
// TODO 需要通过修改background-size来实现,这里改duration不起作用
}, duration + 250);
}, startDelay);
});

return shouldDisplay ? (
<TypingAnimationContainer
count={content.length}
duration={duration}
showCursor={showCursor}
>
{content}
</TypingAnimationContainer>
) : (
<></>
);
}

0 comments on commit e00020c

Please sign in to comment.