You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue环境下全页文本一键转Bionic Reading格式的问题求助

实现Vue环境下的Bionic Reading文本转换(解决嵌套标签、元素排除问题)

核心思路

要解决你的问题,关键是精准遍历DOM文本节点,跳过不需要处理的元素(button、br、a、带自定义类的span、导航图标等),只对纯文本内容进行Bionic格式转换,避免破坏原有DOM结构。

实现步骤

1. 定义排除规则和转换逻辑

先明确需要排除的元素:

  • 功能元素:button、br
  • 已有格式元素:a、带自定义类的span
  • 导航图标:假设你的导航图标容器类为.navbar-icons,直接跳过该容器下的所有节点

然后编写文本转换函数,实现Bionic规则:

  • 拆分单词,排除纯数字
  • 用span包裹每个单词,前半部分用带font-weight-bold类的b标签包裹

2. Vue组件完整实现

<template>
  <div class="app-container">
    <!-- 导航栏示例 -->
    <nav class="navbar">
      <div class="navbar-icons">
        <span class="icon">🏠</span>
        <span class="icon">🔍</span>
      </div>
      <button @click="toggleBionic">切换Bionic阅读</button>
    </nav>

    <!-- 内容区域 -->
    <main class="content">
      <p>这是一段包含<a href="#">链接</a>和<br>换行的文本,还有数字1234需要排除。</p>
      <ul>
        <li>列表项1:包含<span class="highlight">高亮文本</span></li>
        <li>列表项2:纯文本内容</li>
      </ul>
    </main>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isBionicActive: false
    }
  },
  methods: {
    toggleBionic() {
      this.isBionicActive = !this.isBionicActive
      const contentEl = document.querySelector('.content')
      if (this.isBionicActive) {
        this.applyBionicReading(contentEl)
      } else {
        this.restoreOriginalContent(contentEl)
      }
    },
    // 保存原始内容,用于切换回正常模式
    restoreOriginalContent(el) {
      const savedContent = el.getAttribute('data-original-content')
      if (savedContent) {
        el.innerHTML = savedContent
        el.removeAttribute('data-original-content')
      }
    },
    applyBionicReading(el) {
      // 先保存原始内容
      if (!el.getAttribute('data-original-content')) {
        el.setAttribute('data-original-content', el.innerHTML)
      }

      // 定义需要排除的元素选择器
      const excludeSelectors = ['button', 'br', 'a', 'span[class]', '.navbar-icons']
      const excludeElements = new Set(el.querySelectorAll(excludeSelectors))

      // 递归处理节点
      const processNode = (node) => {
        // 如果是需要排除的元素,直接返回
        if (excludeElements.has(node) || node.closest('.navbar-icons')) return

        if (node.nodeType === Node.TEXT_NODE) {
          const text = node.textContent.trim()
          if (!text) return

          // 拆分单词,排除纯数字
          const words = text.split(/\s+/).filter(word => !/^\d+$/.test(word))
          if (!words.length) return

          // 创建容器节点
          const fragment = document.createDocumentFragment()
          words.forEach(word => {
            const wordSpan = document.createElement('span')
            // 计算加粗部分长度:单词长度的1/2,最少1个字符
            const boldLength = Math.max(1, Math.floor(word.length / 2))
            const boldPart = word.slice(0, boldLength)
            const normalPart = word.slice(boldLength)

            const boldEl = document.createElement('b')
            boldEl.className = 'font-weight-bold'
            boldEl.textContent = boldPart
            wordSpan.appendChild(boldEl)
            wordSpan.appendChild(document.createTextNode(normalPart))
            // 添加空格分隔(保留原格式)
            fragment.appendChild(wordSpan)
            fragment.appendChild(document.createTextNode(' '))
          })
          // 替换原文本节点
          node.parentNode.replaceChild(fragment, node)
        } else if (node.nodeType === Node.ELEMENT_NODE) {
          // 递归处理子节点
          Array.from(node.childNodes).forEach(child => processNode(child))
        }
      }

      Array.from(el.childNodes).forEach(child => processNode(child))
    }
  }
}
</script>

<style lang="scss" scoped>
.font-weight-bold {
  font-weight: 700;
}

.navbar {
  display: flex;
  justify-content: space-between;
  padding: 1rem;
  background: #f5f5f5;
  .navbar-icons {
    display: flex;
    gap: 1rem;
    .icon {
      font-size: 1.2rem;
    }
  }
}

.content {
  padding: 2rem;
  p, li {
    line-height: 1.6;
    margin-bottom: 1rem;
  }
}
</style>

3. 关键细节说明

  • 保存原始内容:通过data-original-content属性保存初始HTML,方便切换回正常阅读模式
  • 精准排除元素:使用querySelectorAll获取所有需要排除的元素,通过closest判断是否在导航图标容器内,避免误处理
  • 文本节点处理:只针对TEXT_NODE类型节点操作,不会破坏原有嵌套标签(a、span、br等)
  • 数字排除:通过正则/^\d+$/.test(word)过滤纯数字单词,符合需求

常见问题调整

如果需要调整排除的元素,只需修改excludeSelectors数组即可,比如要排除img标签,就添加'img'到数组中。如果导航图标容器类不是.navbar-icons,替换成你实际的类名即可。

内容的提问来源于stack exchange,提问作者SlayerCat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 04:17:50