laizimo/zimo-article

移动端富文本实践篇(三)

laizimo opened this issue · 0 comments

前言

之前,几篇文章我们了解到了一定的基础知识,如果你还未曾看过,可以点击这个链接观看。

本篇内容主要是讲一下文章中文字部分的处理,如'bold'、'italic'、'blockqueto'、'h1'等,以及分割行的插入和链接的插入和修改等模块的代码分析。那么接下来,我们就源码开始,对于我们上述所概述的知识点进行分析。如果你喜欢我的文章,欢迎评论,欢迎Star~。欢迎关注我的github博客

正文

从这里开始我们就会根据源码来对每个模块的实现,进行深入的分析和探讨。

字体

首先,我们可以来看一下加粗,斜体和删除线的实现。我们先来看一下,源码:

commandSet: ['bold', 'italic', 'strikethrough', 'redo', 'undo'],

exec: function(command){
    const _self = this;
    if(_self.commandSet.indexOf(command) !== -1){
    	document.execCommand(command, false, null);
    }else{
    	let value = '<'+command+'>';
    	document.execCommand('formatBlock', false, value);
    }
},

首先,我们来了解一下document.execCommand()函数,它具备3个参数:

  1. command:可以理解为命令,它具备许多原生的命令,例如: 'bold'、'italic'等。同时,它也需要一些自定义标签的命令。它们之间的区别就是性能问题。之后,再做详细的分析
  2. aShowDefaultUI:是否展示用户界面,一般都会设置成false
  3. value:额外的参数值,一般特殊的命令会需要用到特殊的参数,这时就需要去设置,但是默认为null

讲一下,这里的设计。由于我们需要去区分有参数的命令和无参数的命令,所以我们首先将无参数命令,做成一个集合。每当调用这个函数时,首先回去查找是否这个命令是无参数命令。我们来看两个例子:

//bold

document.execCommand('bold', false, null);

//h1

document.execCommand('formatBlock', false, '<h1>');

那么,你可以讲个上面的优化,然后在调用函数的过程中,使用如下的方式:

//bold

RE.exec('bold');

//h1

RE.exec('h1');

这样整体等代码风格会非常的节俭,之后需要添加无参数命令时,只需要在命令集中增加原生命令就可以实现增加。

此处,在编写函数时使用了一点小技巧,有兴趣的朋友可以学习一下

这个函数可以满足我们接下来的字体处理部分,包括引用块。下面我将列举每个功能的调用方式,当然了,android调用也是一样的。

//bold   加粗

RE.exec('bold');

//italic  斜体

RE.exec('italic');

//strikethrough    删除线

RE.exec('strikethrough');

//h1   h1标签

RE.exec('h1');

//h2   h2标签

RE.exec('h2');

//h3   h3标签

RE.exec('h3');

//h4   h4标签

RE.exec('h4');

//blockquote  引用块

RE.exec('blockquote');

有兴趣的同学也可以将JS部分内容进行扣取,然后自己去进行实现。同时,IOS中的运用也是一致的。

分割行

接下来,我们来聊一下分割行的插入问题。

首先,我们来知道一下如何插入分割行。通过execCommand函数中的insertHtml命令执行,例子:

//hr

document.execCommand('insertHtml', false, '<hr>');

这样子,是可以形成一个分割行,但是,你会发现一个问题——焦点不会置换行

「焦点无法换行」,其感觉就是缺乏一个回车操作。但是,我们不可能要求用户在插入的过程中,都去执行一个回车操作,所以我们需要来讲解一个小知识:

回车操作:在大多数人的认识中,回车就是插入的一个
标签,但是,如果你仔细去观察dom中时,会发现回车操作其实是在后面插入了


这样子的格式块。这里是一个小技巧,在后面插入图片的时候,也会被使用到。

所以,我们需要将代码修改一下:

//hr

document.execCommand('insertHtml', false, '<hr><div><br></div>');

这样,我们就实现了一个正常的分割行的插入,同时也保证了焦点的换行。下面我们需要对上述的操作进行封装,因为这个命令会被频繁的调用,而变化的只是后面value部分。

所以,我们的源码中会进行这样子的封装:

insertHtml: function(html){
	const _self = this;
	document.execCommand('insertHtml', false, html);
},

insertLine: function(){
	const _self = this;
	const html = '<hr><div><br></div>';
	_self.insertHtml(html);
},

其中,这里的insertHtml函数,我们在之后的链接和图片插入过程都会被使用到。所以,我只能提前在这里先分析掉了。

链接

讲完分割行的操作,我们紧接着来分析一下链接的操作。

链接的操作,或许会比较繁琐一点,因为,我们必须保证这个链接的插入和修改。

插入过程会比较简单,我们可以先来看一下源码:

//link

insertLink: function(name, url){
	const _self = this;
	const html = `<a href="${url}" class="editor-link">${name}</a>`;
	_self.insertHtml(html);
}

你会发现,这里需要一个name参数和一个url参数。所以,需要android在调用时,先获取到用户输入的链接名和链接地址。这个具体的样式,在github项目中有展示,所以,你也可以模仿我们样式,在点击插入链接按钮的时候,跳出一个输入表单,然后让用户进行输入,最终获得到相应的数据之后,调用这个值。

这个部分比较简单,主要的逻辑部分还在android部分,有兴趣的同学,可以自行去研究android的源码。

之后,我们需要来阐述一下修改链接部分。

按照惯例,我们也先来看一下修改链接部分的源码:

//change link

changeLink: function(name, url){
	const _self = this;
	const current = _self.cache.currentLink;
	const len = name.length;
	current.innerText = name;
	current.setAttribute('href', url);
	const selection = window.getSelection();
	const range = selection.getRangeAt(0).cloneRange();
	const { startContainer, endContainer } = _self.currentRange;
	selection.removeAllRanges();
	range.setStart(startContainer, len);
	range.setEnd(endContainer, len);
	selection.addRange(range);
}

首先,要明白的是,我们在修改链接部分的逻辑并不是简单的索取name和url这么简单。

从头开始说起的话,我们要继续插入链接之后的话题说起。当我们插入链接之后,我们如果需要修改一个链接的逻辑部分可以看成一下几个步骤:

  • 首先,确定你点击的部分是一个链接(这个部分我们在之前的增加点击事件部分内容中已经讲过了)
  • 之后,你需要将链接的这个部分获取出它的链接名和链接地址显示出来,方便用户修改
  • 然后,重新插入的这一步时,我们需要去控制这个range块(因为你会发现,你不仅仅只是修改这个链接,你还需要保证的是,你在修改之后的焦点位置是正确的,如果你不对这里进行处理的话,就会导致焦点的失去,这个行为本身就是不符合逻辑的)
  • 最后,我们需要根据修改的内容,来调整焦点的位置

所以,看过这个部分的内容之后,我们就可以来理解一下源码中我们所作的处理了。

  1. 首先,我们会在最初点击事件内部保存这个链接节点currentLink,
  2. 之后,我们需要根据我们输入的name和url来修改当前这个节点的innerText内容和href属性。
  3. 最后一步,就是我们之前提到的修改range。因为,我们之前讲过range的基础知识,知道它具备startContainer、startOffset、endContainer、endOffset。这么四个基本属性,那么,我们调整焦点的时候,只需要去调整整个offset距离就可以了,我们只要将startOffset和endOffset都保证与输入内容相同长度,就可以保证这个焦点一定会在链接末尾处。

这里,我们就将修改链接部分的逻辑表述完了,不知道你现在是否清楚我们为什么需要这样去操作了呢。欢迎讨论。

总结

这篇文章中,我们对于字体部分的内容,分割行,最后是链接等三部分的操作做了一个详尽的分析。也将我在开发过程中的思考,写在了里面,可以说是一篇十足的实践类的干货文章。同时,也希望你在看完这篇文章之后,会对富文本操作方面有更加深入的了解,下面一篇文章,讲述的主题是图片操作这块的内容,尽情期待!!!

最后,如果你对我写的有疑问,可以与我讨论。如果我写的有错误,欢迎指正。你喜欢我的博客,请给我关注Star~呦。大家一起总结一起进步。欢迎关注我的github博客。同时也希望你关注我们的项目,github项目地址,谢谢支持