让代码像是打字一样显示在页面上

之前有一个介绍自研产品的任务,落地页需要用代码的形式展示一下安装方法,核心的几个概念,需求就是这样,让代码一行一行展示出来,字符一个个蹦出来,就好像大模型用代码作答一样。 之前没有考虑好,写的很匆促,今天在官网上又看到自己写的那个玩意,想想是不是可以写的再稍微好点。😆

我要做的是展示下面的代码(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-classhljs-functionhljs-title这样的类名变成了hljs-stringhljs-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>
  );
}

这里是稍微牺牲了一点性能的,不过我没有想出更好的办法,各位有更好的实现,欢迎前来指导😁

相关推荐
16年上任的CTO11 分钟前
vue2-mixin的定义与和使用
前端·javascript·vue.js·mixin
呦呦鹿鸣Rzh13 分钟前
HTML-表格,表单标签
java·前端·html
柠檬豆腐脑20 分钟前
从前端到全栈:搭建Gitlab并实现自动化部署全栈项目
前端·gitlab
轻口味1 小时前
Vue.js 使用 `teleport` 实现全局挂载
前端·javascript·vue.js
吴小花的博客1 小时前
日期选择控件,时间跨度最大一年。
前端·vue.js·elementui
m0_748248232 小时前
Go-Gin Web 框架完整教程
前端·golang·gin
苏-言2 小时前
深入核心:一步步手撕Tomcat搭建自己的Web服务器
服务器·前端·tomcat
16年上任的CTO2 小时前
vue2-给data动态添加属性
前端·javascript·vue.js·动态添加属性
彭友圈1012 小时前
利用HTML和css技术编写学校官网页面
前端·css·html
飞舞花下3 小时前
一、0-1搭建springboot+vue3前后端分离-前端项目创建
前端