前言

本例基于react,但是实际上就是用原生js做的。兼容性做到了IE9,但是按照这个思路做是可以做到IE8甚至更低的。

需求与最初的思路

当我拿到这个需求的时候以为很简单,就是可以给页面上的文章做记号,比如添加个下划线,或者背景涂色做成荧光笔的样子。

因为只需要兼容IE9,所以window.getSelection是支持的。(IE8及以下有其它的获取选中的方法)

那么思路就是选中文本,点击添加下划线后,通过 window.getSelection.getRangeAt(0) 拿到选中的文本对象,获取到文本后,通过文本对象的 surroundContents 方法来将文本替换为带有class的元素。

初步的实现

思路很简单,代码同样也很简单。

CSS代码:

.custom-underline{
border-bottom: 1px solid #f00;
font-style: normal;
} .nite-writer-pen{
background-color: lightgreen;
border-radius: 5px;
box-shadow: 0 0 10px lightgreen;
font-style: normal;
}

JS代码:

/**
* 用元素替换被选中的文本
*/
var replaceSelectedStrByEle = function(className){
var selecter = window.getSelection();
var selectStr = selecter.toString();
if (selectStr.trim != "") {
var rang = selecter.getRangeAt(0);
var ele = document.createElement("i");
ele.className = className;
ele.textContent = selectStr
rang.surroundContents(ele);
}
} replaceSelectedStrByEle('nite-writer-pen');

天坑出现

上面的思路实在是过于简单,如果是一个很简单的元素,那么这种做法是没有问题的。

但是我们的文章的html结构一般都没有这么简单,比如对于以下情况:

<p>
<p>道可道,非常道。</p>
<p>名可名,非常名</p>
</p>

如果在页面上我选中的操作如下:

那么上面的代码实现就会出现BUG,对于这种跨元素选中的情况,想当然的用元素去替换文本是没用的。

如果你想得更多,比如跨多个元素选中,以及选中元素为更为复杂的html结构,你就会发现这是一个多大的坑。

html结构有多复杂,这个坑就有多深。

思路的僵局与写轮眼

其实天坑也不是完全没有路走,在跨多个元素选中的过程中,我想给选中的内容加样式,那么就需要获取到所有选中的文本节点,并且批量替换成元素。

但是 window.getSelection.getRangeAt(0) 获取到的range对象只能获取到最开始选中的节点和最后选中的节点的。

那么接下来通过选中的最开始的节点和最后的节点获取到所有的文本节点。

思路就是这么个思路,但是实现起来是很复杂的。

面临深坑,肯定不可能硬刚。

毕竟我已经不是当年头铁的愣头青了,做项目是有时间和精力成本的,如果要填掉这个坑,那么加班是不可避免的,最重要的是在有限时间内填的这个坑可能还有各种BUG和兼容性问题。

对待这种天坑,一般就给需求来个做不了三连了。

但是这把我想赢,毕竟这个东西看起来确实简单。

在外人的眼里,如此之简单,分分钟搞定的事情。这都做不了,我还怎么在前端的圈子里继续划水?

所以我要动用程序员的入门技——写轮眼。

然而百度、谷歌无效,根本没有这个解决方案,有的全是些我最初的简单实现。

但是回忆我们以前见到的各种网页应用与场景,很容易就能想到上面的这种操作我们是见过的。

那就是从远古IE时代就已经出现的各种富文本编辑器组件。

目标确认,百度的ueditor,这波我要赢。

分析ueditor与复制

上github两三下拿到ueditor源码,开始读源码分析代码。

中间过程不再多说,精简代码,除去一些不需要的代码和兼容性处理后,拿到了五个文件:

  • browser.js (浏览器版本判断,用于做兼容性处理)
  • domUtils.js (dom操作)
  • dtd.js (节点的类型与元素判断)
  • Range.js (封装的选中范围对象)
  • utils.js (工具类)

即使精简后,代码也不少,大概两三千行。不过其中还有很多注释,压缩后体积并不大。

由于代码比较多,这里就不全部展示了,文章最后会给出github的地址。

这里只给出最后的使用代码:

/**
* 添加下划线
*/
addUnderline = () => {
this.replaceSelectedStrByEle(styles['custom-underline'])
} /**
* 启用荧光笔
*/
enableNiteWriterPen = () => {
this.replaceSelectedStrByEle(styles['nite-writer-pen'])
} /**
* 用元素替换被选中的文本
*/
replaceSelectedStrByEle = (className) => {
var getRange = () => {
var me = window;
var range = new Range(me.document); var sel = window.getSelection();
if (sel && sel.rangeCount) {
var firstRange = sel.getRangeAt(0);
var lastRange = sel.getRangeAt(sel.rangeCount - 1);
range.setStart(firstRange.startContainer, firstRange.startOffset)
.setEnd(lastRange.endContainer, lastRange.endOffset);
}
return range
}
var range = getRange();
range.applyInlineStyle('i', {
class: className
});
range.select();
}

使用起来还是比较简单的。

对i元素的处理做的一些修改

如果我们选中的是已经被包裹在i元素中的一段文本,那么调用后会发现并没有加上class属性。

这是因为富文本编辑器和我们的需求不一样,通过操作想把选中文本变为i元素,而文本外面本来就是i元素了,自然不会进行剩下的操作。

在填充元素的最后会有一个mergeToParent的操作,他会在填充元素的标签和其父级元素的标签一样后将元素替换为文本。

if (parent.tagName == node.tagName || parent.tagName == "A") {
//...
}

那么这里我们要修改源码加上一个判断

if ((parent.tagName == node.tagName && parent.className == node.className) || parent.tagName == "A") {
//...
}

至于其它的逻辑保持不变即可。

为支持回退操作做的一些修改

这里getRange拿到的对象range在选中内容并替换样式后依然可以使用。

可以调用

range.removeInlineStyle('i')

移除之前添加的样式。

也就是说这里如果使用一个命令模式之类的,是可以实现回退操作的。

不过这里还是有一个坑,就是removeInlineStyle会移除掉选中内容中所有的i元素,于是我修改了

Range.js中removeInlineStyle这个方法,多加了一个className参数,每次去掉i元素时都会判断是否参数等于className。

然后我们调用时就是

range.removeInlineStyle('i',styles['nite-writer-pen'])

总结

作为一个暗藏天坑的小需求,搞定之后其实还挺有成就感的。

粗略阅读了源码后才发现如果自己做会有多坑,基本上没个三五天下不来,并且在多掉了N根头发后仍然会发现到处都是考虑不周和各种BUG。

那么最后贴上代码的github地址:项目地址

如文中有谬误,或者您有更有趣的玩法,还望不吝赐教。

天坑之路:用js给选中文字添加样式的更多相关文章

  1. js 控制选中文字

     //脚本获取网页中选中文字 var word = document.selection.createRange().text;  //获取选中文字所在的句子 var range =  documen ...

  2. 不让复制是不可能的----js获取选中文字

    在360百科.知乎上经常会遇见禁止复制文本的情形,这能挡住一部分人复制,却挡不住程序员的复制. HTML都给我了,难道一小段文本我都拿不下来吗? F12打开控制台,然后选中文本,在控制台下粘贴以下代码 ...

  3. js实现选中文字 分享功能

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  4. 用css改变鼠标选中文字的样式

    打开一个页面,选中一段文字,会出现系统默认的蓝色背景和白色文字,如下图: 那么 这种选中效果我们怎么去自定制呢,比如我想让文字选中的时候背景是绿色,文字是白色 我们在css文件中插入如下代码: ::- ...

  5. js实现分页列表添加样式

    <script> var dUrl=window.location.href; var cUrl=(dUrl.substring(0, dUrl.indexOf('list_'))); v ...

  6. 原生js移除或添加样式

    样式效果如下,点击商品详情 添加样式active 代码 <!doctype html> <html lang="en"> <head> < ...

  7. js 选中文字

    选中文字,文字背景是蓝色 当前点击的元素: var e = e || event; var tag = e.target || e.srcElement; 选中文字:window.getSelecti ...

  8. [js高手之路]Node.js+jade+mongoose实战todolist(分页,ajax编辑,删除)

    该系列文章索引: [js高手之路]node js系列课程-创建简易web服务器与文件读写 [js高手之路]node js系列课程-图解express+supervisor+ejs用法 [js高手之路] ...

  9. marquee实现文字移动效果;js+div实现文字无缝移动效果

    1.marquee实现文字移动: <marquee width="220px;" scrollamount="5" onmouseover="t ...

随机推荐

  1. 智能POS相关FAQ

    1.安卓智能POS(一体机)的口碑点餐已知问题: 1.由于口碑的组合套餐接口不稳定,强烈建议商户不要使用组合套餐商品.已开通口碑后付的门店,如果有组合套餐商品,暂时不要使用组合套餐商品:有组合套餐需求 ...

  2. 获取Bing每日图片API接口

    bing图片每日更新,对于这一点感觉挺不错的,如果能够把bing每日图片作为博客背景是不是很不错呢?首先我们进入Bing首页,会发现自动转到中国版.不过这没关系,中国版更符合国情,速度也比国际版快一些 ...

  3. jar包导入导出

    java项目: 在classLoader加载jar和class的时候,是分开加载的,一般jar导入分两种: 1.在web-inf下的lib中直接引入 2.在user library上引入 无论以上哪种 ...

  4. 后台登录(包含验证码)的php代码实现

    login.html文件 <html> <title>login in</title> <body> <form action="han ...

  5. kafka_2.11-2.0.0_安装部署

    参考博文:kafka 配置文件参数详解 参考博文:Kafka[第一篇]Kafka集群搭建 参考博文:如何为Kafka集群选择合适的Partitions数量 参考博文:Kafka Server.prop ...

  6. Docker: docker image常用命令实战

    #docker列出镜像[root@192 ~]# docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEnginx latest 881bd08c0b08 ...

  7. 使用Razor Generator构建模块化ASP.NET MVC应用程序

    在构建Web应用程序的时候,我们很难做到模块化的开发,这是因为Web应用程序不仅仅包含编译的C#代码,还包含了js.css和aspx等资源. 在ASP.NET MVC中,我们发布应用程序的时候,还会包 ...

  8. Iframe框架+table布局 +div布局实例

    <td colspan="2" style="width: 80%"> <iframe src="http://www.baidu. ...

  9. 【Teradata】配置PE和AMP(congfig和reconfig工具、vprocmanager)

    The Reconfiguration and Configuration utilities are used to define the AMPs and PEs that operate tog ...

  10. wrk 压力测试 http benchmark POST接口

    简单的 http 性能测试工具 wrk.git 一个简单的 http benchmark 工具, 能做很多基本的 http 性能测试. wrk 的一个很好的特性就是能用很少的线程压出很大的并发量. 原 ...