惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
V
V2EX
C
Check Point Blog
GbyAI
GbyAI
D
Docker
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog RSS Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
T
Troy Hunt's Blog
博客园 - Franky
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Security Blog
Microsoft Security Blog
P
Privacy & Cybersecurity Law Blog
WordPress大学
WordPress大学
The Cloudflare Blog
S
SegmentFault 最新的问题
Latest news
Latest news
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
I
InfoQ
博客园 - 【当耐特】
NISL@THU
NISL@THU
A
About on SuperTechFans
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Scott Helme
Scott Helme
雷峰网
雷峰网
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
V
Vulnerabilities – Threatpost
Security Archives - TechRepublic
Security Archives - TechRepublic
A
Arctic Wolf
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
IT之家
IT之家
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
SecWiki News
SecWiki News
大猫的无限游戏
大猫的无限游戏
S
Security Affairs
The Register - Security
The Register - Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
LINUX DO - 热门话题
T
Tor Project blog

张鑫旭-鑫空间-鑫生活

1分钟速度了解text-align match-parent声明 « 张鑫旭-鑫空间-鑫生活 光标的形状也能设置了,就是CSS caret-shape属性 « 张鑫旭-鑫空间-鑫生活 pretext与文字四面环绕效果的实现 « 张鑫旭-鑫空间-鑫生活 JSON.rawJSON方法的作用是什么? « 张鑫旭-鑫空间-鑫生活 如何使用CSS判断鼠标从哪个方向进入元素? « 张鑫旭-鑫空间-鑫生活 借助mediabunny纯JS实现视频水印、剪裁、合成等功能 « 张鑫旭-鑫空间-鑫生活 务必谨慎使用JS WeakRef弱引用 « 张鑫旭-鑫空间-鑫生活 CSS六边形头像的实现与蜂巢布局 « 张鑫旭-鑫空间-鑫生活 CSS corner-shape与背景底纹技术 « 张鑫旭-鑫空间-鑫生活 浅学WebTransport API:下一代Web双向通信技术 « 张鑫旭-鑫空间-鑫生活 HTML dialog元素新支持了closedBy属性 « 张鑫旭-鑫空间-鑫生活 HTML interestfor属性与悬停popover交互效果 « 张鑫旭-鑫空间-鑫生活 CSS3模拟window7炫酷界面效果展示 « 张鑫旭-鑫空间-鑫生活 图片旋转+剪裁js插件(兼容各浏览器) « 张鑫旭-鑫空间-鑫生活 纯js无图片无外链CSS图片幻灯片播放插件 « 张鑫旭-鑫空间-鑫生活 CSS border三角、圆角图形生成技术简介 « 张鑫旭-鑫空间-鑫生活 CSS “渐进增强”在web制作中常见应用举例 « 张鑫旭-鑫空间-鑫生活 让IE6/IE7/IE8浏览器支持CSS3属性 « 张鑫旭-鑫空间-鑫生活 jQuery – 高亮动态显示页面HTML代码插件 « 张鑫旭-鑫空间-鑫生活 翻译 – 逐渐消失的Flash网站 « 张鑫旭-鑫空间-鑫生活 CSS reset的重新审视 – 避免样式重置 « 张鑫旭-鑫空间-鑫生活 新浪微博插入话题后部分文字选中的js实现 « 张鑫旭-鑫空间-鑫生活 JavaScript实现http地址自动检测并添加URL链接 « 张鑫旭-鑫空间-鑫生活 CSS实现兼容性的渐变背景(gradient)效果 « 张鑫旭-鑫空间-鑫生活 CSS content内容生成技术以及应用 « 张鑫旭-鑫空间-鑫生活 CSS gradient渐变之webkit核心浏览器下的使用 « 张鑫旭-鑫空间-鑫生活 CSS渐变之CSS3 gradient在Firefox3.6下的使用 « 张鑫旭-鑫空间-鑫生活 区分IE8/IE7/IE6及其他浏览器-CSS “\9″hack « 张鑫旭-鑫空间-鑫生活 CSS实现跨浏览器兼容性的盒阴影效果 « 张鑫旭-鑫空间-鑫生活 CSS3&HTML5各浏览器支持情况一览表 « 张鑫旭-鑫空间-鑫生活 翻译 – CSS Sprites:实用技术还是生厌之物? « 张鑫旭-鑫空间-鑫生活 纯CSS实现易拉罐3D滚动效果 « 张鑫旭-鑫空间-鑫生活 纯CSS实现侧边栏/分栏高度自动相等 « 张鑫旭-鑫空间-鑫生活 CSS+JavaScript实现页面不同布局的切换 « 张鑫旭-鑫空间-鑫生活 JavaScript实现最简单的拖拽效果 « 张鑫旭-鑫空间-鑫生活 自己写的无图片版jQuery zxxbox弹出框插件 « 张鑫旭-鑫空间-鑫生活 中国古代道家思想与网页重构的思考 « 张鑫旭-鑫空间-鑫生活 让所有浏览器支持HTML5 video视频标签 « 张鑫旭-鑫空间-鑫生活 纯CSS实现各类气球泡泡对话框效果 « 张鑫旭-鑫空间-鑫生活 JS HEX十六进制与RGB, HSL颜色的相互转换 « 张鑫旭-鑫空间-鑫生活 CSS实现兼容性的渐变、高光等文字效果 « 张鑫旭-鑫空间-鑫生活 去除冗余 – 精简您的CSS样式代码 « 张鑫旭-鑫空间-鑫生活 对overflow与zoom”清除浮动”的一些认识 « 张鑫旭-鑫空间-鑫生活 jQuery Pagination Ajax分页插件中文详解 « 张鑫旭-鑫空间-鑫生活 absolute绝对定位的非绝对定位用法 « 张鑫旭-鑫空间-鑫生活 网页布局思想浅议-淘宝新版首页为实例 « 张鑫旭-鑫空间-鑫生活 回流与重绘:CSS性能让JavaScript变慢? « 张鑫旭-鑫空间-鑫生活 CSS float浮动的深入研究、详解及拓展(二) « 张鑫旭-鑫空间-鑫生活 CSS float浮动的深入研究、详解及拓展(一) « 张鑫旭-鑫空间-鑫生活 60个极富创造性的震撼的电影海报设计 « 张鑫旭-鑫空间-鑫生活 jQuery Lightbox(balupton版)图片展示插件讲解 « 张鑫旭-鑫空间-鑫生活 博客内嵌flash音乐播放器代码、下载与使用 « 张鑫旭-鑫空间-鑫生活 CSS3 animate实现图片墙3D翻转效果 « 张鑫旭-鑫空间-鑫生活 图片旋转效果的一些研究、jQuery插件及实例 « 张鑫旭-鑫空间-鑫生活 CSS3 border-image详解、应用及jQuery插件 « 张鑫旭-鑫空间-鑫生活 jQuery-两款不同原理的圆角插件讲解 « 张鑫旭-鑫空间-鑫生活 页面可用性之outline轮廓外框的一些研究 « 张鑫旭-鑫空间-鑫生活 CSS3 transition实现超酷图片墙动画效果 « 张鑫旭-鑫空间-鑫生活 HTML CSS列表元素ul,ol,dl的研究与应用 « 张鑫旭-鑫空间-鑫生活 jQuery图片文本滚动切换插件jCarousel中文翻译与详解 « 张鑫旭-鑫空间-鑫生活 酷酷的jQuery鼠标悬停图片放大切换显示效果 « 张鑫旭-鑫空间-鑫生活 IE6下z-index犯癫不起作用bug的初步研究 « 张鑫旭-鑫空间-鑫生活 checkbox复选框的一些深入研究与理解 « 张鑫旭-鑫空间-鑫生活 jQuery之使用slideToggle实现垂直下拉菜单 « 张鑫旭-鑫空间-鑫生活 jQuery-innerfade内部列表自动淡入淡出插件 « 张鑫旭-鑫空间-鑫生活 jQuery-火焰灯效果导航菜单 « 张鑫旭-鑫空间-鑫生活 IE下css bug集合-翻译自haslayout.net « 张鑫旭-鑫空间-鑫生活 翻译-IE7/8@font-face嵌入字体与文字平滑 « 张鑫旭-鑫空间-鑫生活 jQuery-马化腾产品设计与用户体验的一些技术实现 « 张鑫旭-鑫空间-鑫生活 像素的世界及其在web开发制作中的应用 « 张鑫旭-鑫空间-鑫生活 css行高line-height的一些深入理解及应用 « 张鑫旭-鑫空间-鑫生活 CSS实现圆角六色渐变自适应按钮详解 « 张鑫旭-鑫空间-鑫生活 我熟知的三种三栏网页宽度自适应布局方法 « 张鑫旭-鑫空间-鑫生活 jQuery照片图像剪裁插件Jcrop中文翻译详解 « 张鑫旭-鑫空间-鑫生活 Ajax Upload多文件上传插件翻译及中文演示 « 张鑫旭-鑫空间-鑫生活 告别图片—使用字符实现兼容性的圆角尖角效果beta版 « 张鑫旭-鑫空间-鑫生活 大小不固定的图片、多行文字的水平垂直居中 « 张鑫旭-鑫空间-鑫生活 jQuery boxy弹出层对话框插件中文演示及讲解 « 张鑫旭-鑫空间-鑫生活 JavaScript实现新浪微博文字放大显示动画效果 « 张鑫旭-鑫空间-鑫生活 翻译:让网络更快一些——最小化浏览器中的回流(reflow) « 张鑫旭-鑫空间-鑫生活 jQuery之addClasas与removeClass使用实例 « 张鑫旭-鑫空间-鑫生活 关于Google圆角高光高宽自适应按钮及其拓展 « 张鑫旭-鑫空间-鑫生活 JavaScript实现图片幻灯片滚动播放动画效果 « 张鑫旭-鑫空间-鑫生活 jQuery-鼠标经过显示大图并跟随鼠标效果插件 « 张鑫旭-鑫空间-鑫生活 关于gif图片(或png8)杂边锯齿的问题 « 张鑫旭-鑫空间-鑫生活 对html与body的一些研究与理解 « 张鑫旭-鑫空间-鑫生活 鲜为人知的一个解决兼容性问题的利器——小数 « 张鑫旭-鑫空间-鑫生活 搜狐白社会似iphone短信对话框效果的优化 « 张鑫旭-鑫空间-鑫生活 关于文字内容溢出用点点点(…)省略号表示 « 张鑫旭-鑫空间-鑫生活 jquery之append与insertBefore使用实例 « 张鑫旭-鑫空间-鑫生活 jQuery之图片关联伸缩效果 « 张鑫旭-鑫空间-鑫生活 jquery之图片左右切换动画效果 « 张鑫旭-鑫空间-鑫生活 jQuery之replace字符串替换实现不同尺寸图片切换 « 张鑫旭-鑫空间-鑫生活 jQuery-实现图片的放大镜显示效果 « 张鑫旭-鑫空间-鑫生活 jQuery-单击文字或图片内容放大显示效果插件 « 张鑫旭-鑫空间-鑫生活 reflection.js-实现图片投影倒影效果js插件 « 张鑫旭-鑫空间-鑫生活 jQuery-很酷的弹出层效果js插件 « 张鑫旭-鑫空间-鑫生活 复选框单选框与文字对齐问题的研究与解决 « 张鑫旭-鑫空间-鑫生活 css margin的相关属性,问题及应用 « 张鑫旭-鑫空间-鑫生活 IE6下png背景不透明问题的综合拓展 « 张鑫旭-鑫空间-鑫生活
划词评论与Range开发若干经验分享 « 张鑫旭-鑫空间-鑫生活
张鑫旭,zhangxinxu · 2022-09-21 · via 张鑫旭-鑫空间-鑫生活

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=10541 鑫空间-鑫生活
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。

万里挑一

一、先看大屏幕

最近在做划词评论相关的开发,此交互功能的开发还是有一些门槛的,想到可能有小伙伴也会遇到类似需求,决定分享一下若干处理经验。

在具体展开介绍之前,大家可以先看一下一个建议的demo演示效果:https://zhangxinxu.gitee.io/word-comment/

演示页面部分区域截图:

截图效果示意

大家可能注意到了demo的域名是 gitee,没错,我将相关的实现开源了。

项目地址是:https://gitee.com/zhangxinxu/word-comment

不过此项目只有划词这部分的核心功能,评论一笔带过,实际生产过程中,评论和划词是有很多联动的,大家可以基于此项目的demo结合自己业务完成最终的开发。

好,下面开始正式讲讲实现过程中遇到的难点和技术实现。

二、先了解选区和范围的基本概念

在 Web 中,选区指 Selection,范围指 Range

选区概念更大一点,一个选区中可能有多个范围,也就是可以从Selection中获取Range。

Selection和Range都提供了很多属性和方法,可以让我们对选区进行增删改等操作。

在过去,需要兼容IE的时代,选区和范围是比较混乱的,因为IE有自己的一套,其他浏览器有自己的一套。

现在IE已经长眠,就不需要那么多顾虑了,直接学习标准的 API 即可,幸福啊!

IE死亡

而划词评论的实现,本质上就是Selection、Range以及DOM之间的一些处理操作,换句话说,其实就是下面三个东西之间的舞台戏。

// 我是范围
const selection = document.getSelection();
// 我是选区
const range = selection.getRangeAt(0);
// 我是元素
const container = document.querySelector('.xxx');

而所谓的“操作”,其实就是根据需求,让各个API在合适的位置执行而已,所以,需求实现的难点就在于对API掌握的熟悉程度。

所以,你只需MDN文档看一天,各个API试用一遍,结合本文内容,那么什么划词功能实现妥妥的。

且,不仅是划词评论,以后各种与选区相关的开发都是信手拈来。

这里推荐一篇关于选区介绍的好文:Web 中的“选区”和“光标”

我十几年前也写过关于Range的文章,不过内容已经太老了,IE时代的产物,如果你的项目还需要兼容IE浏览器,可以看看。

好了,热身足够了,可以进入正题了,基于实例学习,这个是大多数开发者喜欢的学习方式。

//zxx: 如果你看到这段文字,说明你现在访问是不是原文站点,更好的阅读体验在这里:https://www.zhangxinxu.com/wordpress/?p=10541(作者张鑫旭)

三、难点、问题与解决

1. 选区的居中定位

选择一段文字,然后显示添加评论的按钮,然后按钮要在选区的中间,如下图所示:

添加按钮居中

请问该如何实现?

此定位实现的关键就是需要知道选区的位置和大小,最好要有现成的API,那浏览器提供了相关的API了吗?

提供了!

我们打开MDN文档,可以看到下图所示的这个名为getBoundingClientRect的API。

Range Bounding API

我们就可以借助这个API进行绝对定位了。

const selection = document.getSelection();
const range = selection.getRangeAt(0);
const boundRange = range.getBoundingClientRect();
// 评论按钮就可以基于boundRange绝对定位啦

然而,事情并没有这么简单,选区不一定是规整的一行,可能是跨行的,就像下面这样:

选区跨行示意

此时,如果还是居中显示,那就会有问题,因为定位的评论按钮的箭头并没有指向选区,而是非选区的文字,此时该怎么办呢?

我的解决方法是直接定位到第一行选区的最右侧,就像这样:

选区右侧定位

判断逻辑很简单,只要选区的高度超过一行选区应有的高度即可,示意:

if (boundRange.height > 30) { 
    // 认为是跨行选区 
    // 评论按钮右侧悬浮定位 
}

然而,事情还没有结束,如果用户是双击选择,则极有可能会自带一个换行符(在特定的布局结构下),这个换行符会让选区的位置和视觉上表现不一致,也就是看起来只是现在了几个文字,实际上选择的是整行,如下图示意:

选区位置

此时,需要对选区进行特殊处理,也就是修改选区,具体实现参见“3. 双击全选的特殊处理”。

2. 确定划词的起止位置

也就是当前选区的起止位置在整个段落中的索引值,例如,有个段落是“一船秋梦压星河”,则“秋梦”的起始索引就是1,结束索引就是3。

这个值是发给后端保存用的。

这样,下一次页面载入的时候,我们就可以基于这个起止位置,让对应的内容高亮显示了。

好,概念知晓了,具体该如何实现呢?

或者说,浏览器有没有提供原生的起止位置API呢?

还真有!不过不能直接使用!

这是什么意思呢?

看下图,Selection API提供了几个偏移属性。

selection 偏移 API

Range API 也有几个偏移属性。

range offset相关API

但是这些偏移都是相对于某个节点的,不一定是整个段落元素的偏移值,因此,不能直接拿来使用。

举个例子,有如下HTML代码:

<p>
    前面文字<i>标签元素</i>后面文字
</p>

如果文字的选区是“后面”这两个字,那么Selection的anchorNode就会是:#Text后面文字,anchorOffset也会是0,截图示意:

#text节点示意

偏移offset是0

正确做法是对外部的段落元素进行节点遍历,如果节点(或节点的子节点)匹配anchorNode,则之前所有文字的长度加上anchorOffset就是真实的起始索引值。

下面就是实现的代码:

const selection = document.getSelection();
const range = selection.getRangeAt(0);
let startNode = range.startContainer;
let startOffset = range.startOffset;
// 起始位置的计算
let startIndex = 0;
let loopIndex = function (dom) {
  [...dom.childNodes].some(function (node) {
    if (!node.textContent) {
      return;
    }
    // 节点匹配了
    // 不再遍历
    if (node == startNode) {
      startIndex += startOffset;

      return true;
    }

    if (startNode.parentNode == node) {
      loopIndex(node);

      return true;
    }
    
    startIndex += node.textContent.length;
  });
};

// container是内容的容器元素
loopIndex(container); 

// 结束索引
let endIndex = startIndex + selection.toString().trim().length;

3. 双击全选的特殊处理

双击全选的选区和框选全部去文字的选区有可能是不一样的。

注意这里的措辞是有可能!

关于CSS布局对选区范围的影响,这个我虽然有研究过,不过都是10年前的事情了,还是IE浏览器为王的年代,详见“不同CSS布局实现与文字鼠标选择的可用性”一文,其中的知识已经跟不上时代了,而目前主流布局,以及各种内容混杂下的文字选区,我尚未深入研究过,暂时还没摸清楚其中的规律

所以,双击全选可能有坑的问题,在某些布局条件下会出现,通常,布局越复杂,内容越多,越可能选区不干净。

好,回到这里。

就我自己的这个项目开发而言,就遇到了双击文字的时候,会连同其他元素的文字一起选择的问题(当祖先元素设置了 user-select:none 的时候,没错,none范围选择,有点反直觉),或者选择的内容最后多了个换行符(会自动变成空格)的问题。

平时复制粘贴,上面这种问题不大。

但是,如果是划词评论这种对选区要求比较高的场景,上面的现象会有大问题,比方说此时选区的anchorNode或者focusNode也不是文本,而是更上层的元素,原本的执行逻辑就会出bug。

怎么办呢?只能对这样的选区进行专门的处理了。

处理方法很简单,重新设置Range的内容,然后再使用这个新的Range进行处理(可以是改变Range选区的节点,或者是改变Range选区的起止点,均有对应的API)。

此时,虽然视觉上用户是无感知的,但实际上,代码已经做了很多事情。

处理代码示意如下,也可以参见 gitee 项目中的 utils.js

// eleTarget 就是内容所在的容器元素
// 获得选区
const selection = document.getSelection();
let selectContent = selection.toString();
let selectContentTrim = selectContent.trim();

if (!selectContentTrim) {
  return;
}

// 如果有超出范围的内容
if (eleTarget.textContent.indexOf(selectContentTrim) == -1) {
  return;
}

const range = selection.getRangeAt(0);

// 重新修改选区
if (selectContent != selectContentTrim && eleTarget.textContent.trim() == selectContentTrim) {
  // 如果纯文本,使用当前节点
  if (!eleTarget.children.length) {
    range.selectNode(range.startContainer);
  } else {
    // 如果包含子元素,则改变选区的起止点
    range.setStartBefore(eleTarget.firstChild);
    range.setEndAfter(eleTarget.lastChild);
  }
  
  selectContent = selectContentTrim;
}

接下来,就可以按照常规的文字选区进行处理就好了。

4. 反向高亮标记的实现

有了选区的起止位置,那重新进入页面的时候,如果让对应位置的文字高亮呢?

很显然,我们需要创建新的range,然后外面包裹一个高亮的span元素,这里我们需要用到Range对象的surroundContents() API,此API可以让裸露的文字外面包裹HTML元素,而不至于影响其他文本内容。

好,下面的难点就变成了如何基于startIndexendIndex创建精准的选区,这个还有些麻烦,因为Range的起止点创建需要精确找到对应的节点元素和偏移位置。

下面代码是实现的示意:

function getNodeAndOffset(dom, start = 0, end = 0){
    const arrTextList = [];
    const map = function(chlids){
      [...chlids].forEach(el => {
        if (el.nodeName === '#text') {
          arrTextList.push(el)
        } else if (el.textContent) {
          map(el.childNodes)
        }
      })
    }
    map(dom.childNodes);

    let startNode = null;
    let startIndex = 0;
    let endNode = null;
    let endIndex = 0;
    // 总的字符长度
    let total = startIndex;

    // 计算长度
    arrTextList.forEach(function (node) {
      if (startNode && endNode) {
        return;
      }
      let length = node.textContent.length;
      // 当前节点,总的长度范围
      const range = [total, total + length];
      // 看看,start和end有没有在其中
      // start在这个范围中
      // 可以确定startIndex了
      if (!startNode && start >= range[0] && start < range[1]) {
        startNode = node;
        startIndex = start - total;
      }
      // '我要' (0, 2)
      if (!endNode && end > range[0] && end <= range[1]) {
        endNode = node;
        endIndex = end - range[0];
      }
      total = total + length;
    });

    if (!startNode || !endNode) {
      return null;
    }

    return [startNode, startIndex, endNode, endIndex];
}

var startIndex = obj.startIndex;
var endIndex = obj.endIndex;

// 创建 range
const range = document.createRange();
// container是容器元素
const nodes = getNodeAndOffset(container, startIndex, endIndex);
if (nodes) {
   range.setStart(nodes[0], nodes[1]);
   range.setEnd(nodes[2], nodes[3]);
}

其中实现的核心就是getNodeAndOffset()方法,大家可以直接复制粘贴拿来使用。

5. 编辑时候的起止点和内容实时保存

类似文档这种可编辑的划词评论,当对应内容修改的时候,需要实时保存选区的起止点和内容,此时,相关信息如何获取呢?

下面这段代码就是我使用的算法:

function getContentAndIndexList (target, selector) {
    const divTmp = document.createElement('div');
    // 替换
    divTmp.innerHTML = target.innerHTML;
    // 最终返回的数据
    let operateCommentsList = [];
    // 遍历与匹配
    const getRange = function () {
        let eleWrod = divTmp.querySelector(selector);

        if (!eleWrod) {
            return;
        }

        let text = '';
        [...divTmp.childNodes].some(function (node) {
            if (node === eleWrod) {
                const selectContent = node.textContent;
                operateCommentsList.push({
                    selectContent: selectContent,
                    startIndex: text.length,
                    endIndex: text.length + selectContent.length,
                    // 这里的 gid 命名是根据业务走的,你可以使用其他名称 
                    gid: Number(node.dataset.gid)
                });

                // 节点替换
                node.replaceWith.apply(node, [...node.childNodes]);

                // 继续遍历
                getRange();

                return true;
            }

            text += node.textContent;
        });
    };

    getRange();

    return operateCommentsList;
}

原理为:创建个临时的div对象,完全克隆元素内容,然后遍历所有的高亮元素,并依次把此高亮元素替换成子元素,从而获得所有的高亮划词的数据。

正好和不断使用surroundContents()进行高亮的做法相反。

好,有了以上几个核心难点的解决,再配合一些常见的交互逻辑,就能实现完整的划词功能了。

具体的实现逻辑和代码参见本文一开头展示的那个 gitee 项目。

四、结束语

可能有人注意到了,加上这篇文章,我这是3天3连发了,高产似啥啥。

奇怪,博主!我还以为你沉迷钓鱼无法自拔,以至于不再更新博客了。

NoNoNo!虽然钓鱼花的时间确实比以前多多了,但那是把之前写小说的时间腾出去的。

之所以这两个月文章更的完全没有之前勤快,是因为这两个月在忙着写《CSS选择器第二版》,这不,上周刚交完稿,这周我就爆更来了。

有时间了。

不过,别得意,等10月过后,我还要写《CSS世界修订版》,又要几个月忙得不可开交了。

所以,趁着10月还有一周,我打算再爆更几篇。

尽请期待~

本文为原创文章,欢迎分享,勿全文转载,如果实在喜欢,可收藏,永不过期,且会及时更新知识点及修正错误,阅读体验也更好。
本文地址:https://www.zhangxinxu.com/wordpress/?p=10541

(本篇完)