你可能不知道的vitepress扩展-2

324 阅读7分钟

前言

在我编写的在线字典网站中,我碰到了如下一个需求,如下图所示:

font-meego.jpeg

让我为你叙述这个需求需要做什么。

需求分析

在markdown文档中,我希望实现一个语音图标,点击语音图标就可以阅读对应的拼音,虽然这只是一个简单的小需求,但具体实现还是有点复杂的。

实现思路

我们可以将这个小需求划分成两步:

  1. 语音图标的来源?
  2. 如何实现朗读?

对于语音图标,我们可以导入element plus图标组件,然后注册到vitepress中,然后在markdown文档中编写这个组件。

具体实现

导入element plus组件

首先,我们需要安装相关依赖,打开终端输入如下命令安装相关依赖:

pnpm add element-plus @element-plus/icons-vue

接着我们需要在vitepress中导入element plus组件,根据theme api,我们需要在.vitepress目录下新建一个theme目录以及一个index.mts文件,如下图所示:

截屏2025-01-29 下午8.01.51.png

在该文件中,我们需要导出一个对象,该对象我们需要用到2个属性,既extends和enhanceApp属性,extends用于继承默认的主题,而enhanceApp是一个函数,函数暴露了一个app属性,而这个属性就是一个vue实例,我们可以通过这个实例来注册一个组件。现在我们的文件内的代码应该如下所示:

// .vitepress/theme/index.mts
// 导入element plus组件和样式
import ElementPlus from "element-plus";
import "element-plus/theme-chalk/index.css";
// 导入默认主题
import DefaultTheme from "vitepress/theme";
//导入图标组件
import * as ElementPlusIconsVue from '@element-plus/icons-vue';

export default {
  extends: DefaultTheme, // 继承默认主题
  enhanceApp({ app }) {
    // 注册element组件
    app.use(ElementPlus);
    // 注册element图标组件
    for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
      app.component(key, component)
    }
  },
};

以上,我们通过app.use和app.component来注册element plus组件和element plus图标组件,我们通过从vitepress/theme中导入默认的依赖。

注册自定义组件

这样我们就可以使用element组件和图标组件了,接下来,我们需要注册一个语音朗读组件。如下所示:

// .vitepress/theme/index.mts
// ...
import ReadText from "../components/read-text.vue";

export default {
  // ...
  enhanceApp({ app }) {
    // ...
    app.component("ac-read-text", ReadText);
  },
};

我们同样使用app.compoent方法来注册我们的自定义组件。

语音朗读组件的具体实现

接下来我们就要实现一个语音朗读组件,首先我们的语音朗读组件的html结构应该如下所示:

<el-text @click="onReadText">
    <slot></slot>
    <el-icon style="font-size: 20px;" class="cursor-pointer read-text-icon">
      <Microphone />
    </el-icon>
</el-text>

也就是说,我们使用el-text组件包裹这个icon元素,注意这里会有插槽slot,也就是说插槽slot就是我们需要朗读的内容,我们在使用的时候就会如下所示:


// md文档中
<ac-read-text>比</ac-read-text>

这样,我们最终会在“比”字的右边出现一个语音朗读图标,点击这个图标就可以实现朗读“比”字的效果。

我们添加了两个类名,顾名思义就是稍微调整一下样式,样式代码如下所示:

.read-text-icon:hover {
    color:#2396ef;
}
.cursor-pointer {
    cursor: pointer;
}

主要就是添加了一个悬浮和一个鼠标手型效果。

核心朗读逻辑

接下来,我们实现一个朗读的脚本逻辑,核心代码如下所示:

<script setup>
import { ElMessage } from 'element-plus';
function isSpeechSynthesisSupported() {
  return 'speechSynthesis' in window && typeof window.speechSynthesis.speak === 'function';
}
const findParentElement = (el, className) => {
  while (!el?.classList.contains(className)) {
    el = el?.parentElement;
  }
  return el;
};
const onReadText = (e) => {
    const target = e.target;
    if (!target) {
        return;
    }
    if (['svg', 'path', 'el-icon'].includes(target.tagName.toLowerCase())) {
        const element = findParentElement(target,'el-text');
        const text = element.innerText;

        if (!text) return;

        if (!isSpeechSynthesisSupported()) {
            return ElMessage.info('当前环境不支持阅读文本功能!');
        }
        const utterance = new SpeechSynthesisUtterance();
        utterance.text = text;

        // 设置语言为中文
        utterance.lang = 'zh-CN';
        // utterance.rate = 0.4; // 设置语速,范围是0.1到10
        // utterance.pitch = 2; // 设置音调,范围是0到2

        window.speechSynthesis.speak(utterance);
    }

}
</script>

让我们一一来看每一段代码所代表的意思,这段代码主要功能是通过浏览器的语音合成 API 实现对文本内容的朗读,并处理一些基本的用户交互逻辑。下面是这段代码的详细解释:

1. isSpeechSynthesisSupported 函数
function isSpeechSynthesisSupported() {
  return 'speechSynthesis' in window && typeof window.speechSynthesis.speak === 'function';
}

这个函数用于检查当前环境是否支持浏览器的语音合成功能(Speech Synthesis)。具体来说,它检查 window.speechSynthesis 是否存在,以及 window.speechSynthesis.speak 是否是一个函数。语音合成 API 只有在浏览器支持的情况下才能使用,如果返回 true,说明可以使用语音合成,反之则不能。

2. findParentElement 函数
const findParentElement = (el, className) => {
  while (!el?.classList.contains(className)) {
    el = el?.parentElement;
  }
  return el;
};

这个函数用于查找某个元素的父级元素,直到找到一个包含指定类名的元素为止。它接受两个参数:

  • el:要查找的起始元素。
  • className:目标类名。

函数会从 el 开始,逐级查找父元素,直到找到一个具有指定类名的父元素。如果没有找到,它会一直向上查找直到 elnullundefined

3. onReadText 函数
const onReadText = (e) => {
    const target = e.target;
    if (!target) {
        return;
    }
    if (['svg', 'path', 'el-icon'].includes(target.tagName.toLowerCase())) {
        const element = findParentElement(target,'el-text');
        const text = element.innerText;

        if (!text) return;

        if (!isSpeechSynthesisSupported()) {
            return ElMessage.info('当前环境不支持阅读文本功能!');
        }
        const utterance = new SpeechSynthesisUtterance();
        utterance.text = text;

        // 设置语言为中文
        utterance.lang = 'zh-CN';
        // utterance.rate = 0.4; // 设置语速,范围是0.1到10
        // utterance.pitch = 2; // 设置音调,范围是0到2

        window.speechSynthesis.speak(utterance);
    }
}

这个函数主要负责处理文本的朗读。其流程如下:

  1. 事件参数 e: 这是一个事件对象,通常会由用户触发某个操作(如点击)时传入。e.target 是事件触发的目标元素。
  2. 检查 target 元素: 如果目标元素存在,则继续执行后续逻辑。
  3. 过滤 svg, path, el-icon 标签: 如果目标元素是 svg, pathel-icon 标签之一,它会尝试在该元素的父元素中查找一个具有 el-text 类名的元素。
  4. 获取文本内容: 如果找到了具有 el-text 类的父元素,获取其 innerText,即该元素中的文本内容。
  5. 检查文本内容: 如果没有获取到文本内容,则退出函数。
  6. 检查语音合成功能支持: 如果浏览器不支持语音合成功能,则通过 ElMessage.info 弹出提示框,告诉用户当前环境不支持文本阅读。
  7. 语音合成: 如果语音合成功能可用,则创建一个 SpeechSynthesisUtterance 对象,并将要朗读的文本赋值给它。
    • 设置语言为中文(zh-CN)。
    • 你可以设置语速(utterance.rate)和音调(utterance.pitch),但这两行代码被注释掉了。
  8. 开始朗读: 使用 window.speechSynthesis.speak(utterance) 启动语音合成,朗读文本。
总结

这段代码的目的是通过点击某些元素(如 svgpathel-icon)来触发文本朗读功能。它会:

  1. 查找元素中的文本内容。
  2. 检查是否支持语音合成功能。
  3. 使用浏览器的语音合成 API 朗读该文本。

如果当前环境不支持语音合成,它会通过 ElMessage 提示用户当前不支持语音朗读功能。

综合

将以上代码组合一下,就成了我们的ACReadText组件,接下来我们只需要使用该组件即可。

自定义匹配

如果为md文档的每一个需要朗读的字或者拼音,我们都需要去写html标签,那也太麻烦了,接下来还是和前文一样的思路,我们需要通过正则来匹配关键字符串,然后替换成这个组件即可。

为此,我约定了通过readxxxread,也就是说,将想要阅读的文本通过两个read字符包裹,即可渲染成该组件。我们需要修改一下markdown属性,如下所示:

// .vitepress/config.mts
export default defineConfig({
  // ...
  markdown: {
    config: (md) => {
      md.renderer.rules.text = (tokens, idx) => {
        const text = tokens[idx].content;
        // ...
        const transformedReadText = transformedText.replace(/read(.*?)read/g, (_match, p1) => `<ac-read-text>${p1}</ac-read-text>`);

        return transformedReadText;
      };
    },
  },
})

/read(.*?)read/g这个正则表达式和前文同一个原理,这里就不做过多解释。完成以上工作之后,我们就可以在markdown文档里面随便写文档既可以实现语音朗读文本的功能。如下一个示例:

// md文档
read你好read

总结

通过上述的步骤,我们成功地实现了一个语音朗读功能,允许用户点击图标朗读拼音或文字,通过正则表达式处理,我们还优化了使用体验,使得 Markdown 中需要朗读的内容自动转换为组件。