之前有一个介绍自研产品的任务,落地页需要用代码的形式展示一下安装方法,核心的几个概念,需求就是这样,让代码一行一行展示出来,字符一个个蹦出来,就好像大模型用代码作答一样。 之前没有考虑好,写的很匆促,今天在官网上又看到自己写的那个玩意,想想是不是可以写的再稍微好点。😆
我要做的是展示下面的代码(PS我网上随便找的一点Python代码,可行性与否不用在意,演示数据而已)
python
class SimpleBook:
def __init__(self, title, author):
self.title = title
self.author = author
def __str__(self):
return f"'{self.title}' by {self.author}"
class MiniLibrary:
def __init__(self):
self.collection = []
def add_book(self, book):
self.collection.append(book)
print(f"Added: {book}")
# 创建书籍和小型图书馆实例
book1 = SimpleBook("奇幻旅程", "王小小")
my_library = MiniLibrary()
# 添加书籍到图书馆
my_library.add_book(book1)
# 打印特定书籍信息
print(my_library.collection[0])
之前的想法是,每一行都是一个span标签包裹,并且为变量和函数还要给不同的行类样式,有点呆🤣。现在我们直接使用highlight.js
,使用起来那是相当方便,因为我是React,我就下载react-highlight
,它已经把highlight.js
作为依赖了。npm i react-highlight -S
安装好之后,浅浅试一下,当然我们还可以选择自己的代码风格,我这里使用monokai
;
tsx
import Highlight from 'react-highlight';
import 'highlight.js/styles/monokai.css'; // 代码风格
import './index.scss'; // 滚动条,文字大小,居中显示等
const pyCode = `
class SimpleBook:
def __init__(self, title, author):
self.title = title
self.author = author
def __str__(self):
return f"'{self.title}' by {self.author}"
class MiniLibrary:
def __init__(self):
self.collection = []
def add_book(self, book):
self.collection.append(book)
print(f"Added: {book}")
# 创建书籍和小型图书馆实例
book1 = SimpleBook("奇幻旅程", "王小小")
my_library = MiniLibrary()
# 添加书籍到图书馆
my_library.add_book(book1)
# 打印特定书籍信息
print(my_library.collection[0])
`;
export default function CodeTyping() {
return (
<div className="code-stage">
<Highlight className='python-code' language="python">
{pyCode}
</Highlight>
</div>
);
}
做好了展示,接下来做一下打字效果,我的想法是pyCode作为源数据,再用一个变量typedCode作为响应式数据,定时器定时更新下typedCode。
tsx
export default function CodeTyping() {
const [typedCode, setTypedCode] = useState('');
const indexRef = useRef(0); // 使用 useRef 来保存当前索引
useEffect(() => {
const intervalId = setInterval(() => {
if (indexRef.current < pyCode.length) {
setTypedCode((prevTypedCode) => prevTypedCode + pyCode[indexRef.current]);
indexRef.current++;
} else {
clearInterval(intervalId);
}
}, 50); // 调整间隔时间以改变打字速度
return () => clearInterval(intervalId);
}, []);
return (
<div className="code-stage">
{/* 使用 Highlight 组件包裹你的代码,并设置语言属性 */}
<Highlight className='python-code' language="python">
{typedCode}
</Highlight>
</div>
);
}
效果是有了的,但是仔细一看不对劲,代码风格没有了,我检查了一下import 'highlight.js/styles/monokai.css';
这个文件是存在的,那么是别的问题,检查元素才发现,标签类名变化了,由原来的hljs-class
、hljs-function
、hljs-title
这样的类名变成了hljs-string
、hljs-attibute
这样的类名了, 那只能是Highlight组件接收我切割下来的不全的代码,没有做到预先的效果。搜了一圈,有两种解决方案:
- 在打字过程中,使用
<pre>
标签显示普通文本;打字完成后,使用Highlight
组件渲染高亮代码。它存在一个问题,那就是<pre>
标签展示不太美观,会和后面的样子有不小出入,调整比较麻烦; - 第二种方法,直接令Highlight组件重新渲染,使用
key={typedCode.length}
强制Highlight
组件在每次typedCode
更新时重新渲染,这样可以确保Highlight
组件每次都重新解析代码,从而实现动态高亮。
我这里使用第二种方法,下面是完整代码:
tsx
export default function CodeTyping() {
const [typedCode, setTypedCode] = useState('');
const indexRef = useRef(0);
useEffect(() => {
const intervalId = setInterval(() => {
if (indexRef.current < pyCode.length) {
setTypedCode((prevTypedCode) => prevTypedCode + pyCode[indexRef.current]);
indexRef.current++;
} else {
clearInterval(intervalId);
}
}, 50);
return () => clearInterval(intervalId);
}, []);
return (
<div className="code-stage">
{/* 使用 key 强制重新渲染 Highlight 组件 */}
<Highlight key={typedCode.length} className='python-code' language="python">
{typedCode}
</Highlight>
</div>
);
}
这里是稍微牺牲了一点性能的,不过我没有想出更好的办法,各位有更好的实现,欢迎前来指导😁