Open laizimo opened 6 years ago
至上一篇基础常识讲完之后,这次我们将开启新的篇章。本篇我们会来讲述你在操作时需要去增加的监听事件,焦点控制,按钮的状态同步等问题。同时,还需要完成的是当标题栏聚焦时,你需要去控制按钮的禁止点击,例如插入图片按钮等。所以综合而言,本篇还是相对比较重要的。接下来,我们会就上述提到的点进行一一讲述。如果你喜欢我的文章,欢迎评论,欢迎Star~。欢迎关注我的github博客。
首先,我们第一步来讲一下焦点控制的问题。那么,你会认为焦点控制只是简单的使用focus函数聚焦这么简单吗?当然不是。一旦,你这样操作了,你会发现一个问题,你的焦点永远在起始处,往往会造成不良的用户体验。这里我们需要明白的第一点——焦点控制。
我们知道焦点其实就是一个range。每个range都会又一个collapsed去判断,前后是否重叠。一旦,前后重叠了,这个range就会称为焦点。所以,我们如果想去控制焦点,就必须学会控制range块。
回到上一篇的range介绍,我们知道range具备4个常用属性:startContainer、startOffset、endContainer、endOffset等4个属性。我们只要学会如何去控制这4个属性,我们就能将range块变出任意的样子。
那么,我们可以先从「聚焦功能」说起:
这里,我们所谓的聚焦是可以区分为两种,一种是保证聚焦到尾部,另一种就是聚焦到用户输入的位置。
之前,我们已经说过,focus函数使得整个编辑块聚焦,但是它是使得焦点聚焦在起始处,而我们现在需要将整个焦点聚焦到末尾,我们源码中的逻辑可以这样:
我们可以来看一下源码:
focus: function(){ //聚焦 const _self = this; const range = document.createRange(); range.selectNodeContents(_self.cache.editor); range.collapse(false); const select = window.getSelection(); select.removeAllRanges(); select.addRange(range); _self.cache.editor.focus(); }
这里,我们创建了一个range,然后将这个range的节点内容设置为编辑块,之后使用collapse来使得它的先后合并。同时,我们需要去获得选区,将选区中的range都清除掉,再将新创建的range对象添加到选区对象中。最后,使编辑块聚焦。
这时,你去测试一下,你就会发现焦点会自动聚焦到尾部去
之前,我们也讲述过还有一种焦点控制的方式——聚焦到原先用户输入的位置
那么,我们需要如何去完成这一个功能呢?我们首先需要去保存用户输入的焦点。
我们可以先来看一下源码:
saveRange: function(){ const _self = this; const selection = window.getSelection(); if(selection.rangeCount > 0){ const range = selection.getRangeAt(0); const { startContainer, startOffset, endContainer, endOffset} = range; _self.currentRange = { startContainer: startContainer, startOffset: startOffset, endContainer: endContainer, endOffset: endOffset }; } }
这里的saveRange方法就是我们在源码中用来保存Range的方法。其实,它的原理非常的简单:
既然你看到了保存焦点时的原理,那么,相信还原焦点的原理你应该也已经清楚一点了吧。
接下来,我们就来看一下还原焦点的过程:
源码:
reduceRange: function(){ const _self = this; const { startContainer, startOffset, endContainer, endOffset} = _self.currentRange; const range = document.createRange(); const selection = window.getSelection(); selection.removeAllRanges(); range.setStart(startContainer, startOffset); range.setEnd(endContainer, endOffset); selection.addRange(range); }
至此,我们已经将富文本中需要去控制焦点的部分内容分析完了。之后,我们先来看一下按钮的状态同步。
何为状态同步?你或许还没有一个比较清晰的概念。那么,我们给定一个场景,来帮助大家理解一下:
最初,你会按下加粗按钮之后,输入部分的内容会加粗;但是,当你这时发现之前有个地方的内容,需要修改,这时你会点击那个部分进行修改。这时问题来了:在没点击之前,你的加粗按钮是高亮显示的,而点击之后,你首先要确定那个位置是否具备加粗,然后去控制按钮的高亮问题。这就是我们之后需要处理的问题——状态同步。
我们可以先简单的阐述一下状态同步的原理:
我们只需要去获得当前焦点处所含有的标签就可以了。因为我们所插入的bold、italic等都是通过execCommand的命令插入的。同样,document也提供了API让我们来获取当前焦点处的标签。我们可以看一下源码中的这个方法:
getEditItem: function(evt = {}){ const _self = this; const { STATE_SCHEME, CHANGE_SCHEME } = _self.schemeCache; if(evt.target && evt.target.tagName === 'A'){ _self.cache.currentLink = evt.target; const name = evt.target.innerText; const href = evt.target.getAttribute('href'); window.location.href = CHANGE_SCHEME + encodeURI(name + '@_@' + href); }else{ if(e.which == 8){ AndroidInterface.staticWords(_self.staticWords()); } const items = []; _self.commandSet.forEach((item) => { if(document.queryCommandState(item)){ items.push(item); } }); if(document.queryCommandValue('formatBlock')){ items.push(document.queryCommandValue('formatBlock')); } window.location.href = STATE_SCHEME + encodeURI(items.join(',')); } }
这里的源码内容有点复杂,因为我们还有其他的一些情况需要考虑,所以这里我们可以来提取一部分进行分析:
const items = []; _self.commandSet.forEach((item) => { if(document.queryCommandState(item)){ items.push(item); } }); if(document.queryCommandValue('formatBlock')){ items.push(document.queryCommandValue('formatBlock')); } window.location.href = STATE_SCHEME + encodeURI(items.join(','));
这个部分就是实际去获取标签的部分,我们可以先来了解两个API:
queryCommandState: 这个函数返回的boolean类型的值,然后它会去测试当前这个state中是否具备这个标签。
我们可以做个测试:
document.execCommand('bold', false, null); const state = document.queryCommandState('bold'); console.log(state); //true
这样我们就可以明白上面第一部分操作的原理:
queryCommandValue:这个函数也是返回boolean类型的值。我们可以直接来做个测试:
document.execCommand('formatBlock', false, '<h1>'); const value = document.queryCommandValue('formatBlock'); console.log(value); //h1
这里的不同点就是,无参数命令和有参数命令的区别了。类似于h1标签这种,需要我们自定义标签参数的值,往往就需要使用这部分的测试方式,所以,我们也会将获取到的value放入items集合中
最后,一步就是一个通信的问题了。我们之前一篇中,聊到如果js与webView之间进行交互时,可以通过url劫持的方式来完成。我们将这个URL头进行定义,相对应这种特殊的URL头,webView会做相应的处理。
因为URL中添加参数时,都需要将值进行URL编码。所以,我们需要做一个编码的过程。
那么,至此我们提取出来的代码部分讲完了。我们回过头来再去分析一下原来的代码。
有些特殊情况或许你的考虑到:
首先,来看一下修改链接的,我们并不需要去进行状态的同步。所以,我们需要确定点击时,判断这个节点元素是否是A标签,我们可以看一下源码:
if(evt.target && evt.target.tagName === 'A'){ _self.cache.currentLink = evt.target; const name = evt.target.innerText; const href = evt.target.getAttribute('href'); window.location.href = CHANGE_SCHEME + encodeURI(name + '@_@' + href); }
因为,我们需要修改链接,所以需要将当前这个链接的节点保留下来,方便之后的修改;同时,我们也需要向webview传递链接的name和url的信息。使用的方式——URL劫持。
之后,我们需要去考虑的是一些键位,比方说回车操作,删除操作。它们本身也不会去通知webview对其进行监听。键位的话,我们可以考虑按键时的键位code来进行特殊键位的判断,如下:
_self.cache.editor.addEventListener('keyup', (evt) => { if(evt.which == 37 || evt.which == 39 || evt.which == 13 || evt.which == 8){ _self.getEditItem(evt); } }, false);
这里我们对删除键、回车键、左尖括号『<』,右尖括号『>』,做了监听,然后当用户按下这几个键时,都会调用getEditItem的方法。
状态同步的问题我们就聊那么多。之后我们来看一下我们设置的监听事件。
直接先放上源码来让大家看一下:
bind: function(){ const _self = this; document.addEventListener('selectionchange', _self.saveRange, false); _self.cache.title.addEventListener('focus', function(){ AndroidInterface.setViewEnabled(true); }, false); _self.cache.title.addEventListener('blur', () => { AndroidInterface.setViewEnabled(false); }, false); _self.cache.editor.addEventListener('blur', () => { _self.saveRange(); }, false); _self.cache.editor.addEventListener('click', (evt) => { _self.saveRange(); _self.getEditItem(evt); }, false); _self.cache.editor.addEventListener('keyup', (evt) => { if(evt.which == 37 || evt.which == 39 || evt.which == 13 || evt.which == 8){ _self.getEditItem(evt); } }, false); _self.cache.editor.addEventListener('input', () => { AndroidInterface.staticWords(_self.staticWords()); }, false); }
直接按照顺序阐述下去吧!!
selectionchange事件,则是检测选区的变化,因为选区发送变化的时候。往往指定是焦点的变化。
每次焦点发生变化时,都需要去保存当前的range,以便于还原焦点。
focus和blur事件,其实就是需要去控制底栏按钮的可用性。因为我们的界面上面有标题栏,标题栏是不允许插入图片、插入链接、字体操作的。所以这里通过对象映射的方式,提醒webView去禁止底栏显示。
同样的,对于编辑块来说,需要监听blur事件,然后保存原来的焦点。
接下来,就是我们之前所说的点击事件的监听了。首先,点击编辑块时,需要你去保存焦点,同时同步这个位置的状态,调用getEditItem方法。
最后,需要去监听一个输入事件,因为我们需要去同步字体的数量,每当用户输入时,我们就要调用staticWords方法来同步字体的数目。
最后,我们本篇文章的内容都已经分析完了。当然,你也可以细细理解我们在这里说做的所有操作,可以说这篇内容解决了我们在开发富文本编辑器时,大部分的问题。同时,也内涵了我们的思考。希望你能喜欢我们这个项目,同时帮助你的进步。
最后,如果你对我写的有疑问,可以与我讨论。如果我写的有错误,欢迎指正。你喜欢我的博客,请给我关注Star~呦。大家一起总结一起进步。欢迎关注我的github博客。同时也希望你关注我们的项目,github项目地址,谢谢支持
前言
至上一篇基础常识讲完之后,这次我们将开启新的篇章。本篇我们会来讲述你在操作时需要去增加的监听事件,焦点控制,按钮的状态同步等问题。同时,还需要完成的是当标题栏聚焦时,你需要去控制按钮的禁止点击,例如插入图片按钮等。所以综合而言,本篇还是相对比较重要的。接下来,我们会就上述提到的点进行一一讲述。如果你喜欢我的文章,欢迎评论,欢迎Star~。欢迎关注我的github博客。
正文
首先,我们第一步来讲一下焦点控制的问题。那么,你会认为焦点控制只是简单的使用focus函数聚焦这么简单吗?当然不是。一旦,你这样操作了,你会发现一个问题,你的焦点永远在起始处,往往会造成不良的用户体验。这里我们需要明白的第一点——焦点控制。
焦点
我们知道焦点其实就是一个range。每个range都会又一个collapsed去判断,前后是否重叠。一旦,前后重叠了,这个range就会称为焦点。所以,我们如果想去控制焦点,就必须学会控制range块。
回到上一篇的range介绍,我们知道range具备4个常用属性:startContainer、startOffset、endContainer、endOffset等4个属性。我们只要学会如何去控制这4个属性,我们就能将range块变出任意的样子。
那么,我们可以先从「聚焦功能」说起:
这里,我们所谓的聚焦是可以区分为两种,一种是保证聚焦到尾部,另一种就是聚焦到用户输入的位置。
聚焦尾部
之前,我们已经说过,focus函数使得整个编辑块聚焦,但是它是使得焦点聚焦在起始处,而我们现在需要将整个焦点聚焦到末尾,我们源码中的逻辑可以这样:
我们可以来看一下源码:
这里,我们创建了一个range,然后将这个range的节点内容设置为编辑块,之后使用collapse来使得它的先后合并。同时,我们需要去获得选区,将选区中的range都清除掉,再将新创建的range对象添加到选区对象中。最后,使编辑块聚焦。
聚焦还原
之前,我们也讲述过还有一种焦点控制的方式——聚焦到原先用户输入的位置
那么,我们需要如何去完成这一个功能呢?我们首先需要去保存用户输入的焦点。
我们可以先来看一下源码:
这里的saveRange方法就是我们在源码中用来保存Range的方法。其实,它的原理非常的简单:
既然你看到了保存焦点时的原理,那么,相信还原焦点的原理你应该也已经清楚一点了吧。
接下来,我们就来看一下还原焦点的过程:
源码:
至此,我们已经将富文本中需要去控制焦点的部分内容分析完了。之后,我们先来看一下按钮的状态同步。
状态同步
何为状态同步?你或许还没有一个比较清晰的概念。那么,我们给定一个场景,来帮助大家理解一下:
最初,你会按下加粗按钮之后,输入部分的内容会加粗;但是,当你这时发现之前有个地方的内容,需要修改,这时你会点击那个部分进行修改。这时问题来了:在没点击之前,你的加粗按钮是高亮显示的,而点击之后,你首先要确定那个位置是否具备加粗,然后去控制按钮的高亮问题。这就是我们之后需要处理的问题——状态同步。
我们可以先简单的阐述一下状态同步的原理:
我们只需要去获得当前焦点处所含有的标签就可以了。因为我们所插入的bold、italic等都是通过execCommand的命令插入的。同样,document也提供了API让我们来获取当前焦点处的标签。我们可以看一下源码中的这个方法:
这里的源码内容有点复杂,因为我们还有其他的一些情况需要考虑,所以这里我们可以来提取一部分进行分析:
这个部分就是实际去获取标签的部分,我们可以先来了解两个API:
queryCommandState: 这个函数返回的boolean类型的值,然后它会去测试当前这个state中是否具备这个标签。
我们可以做个测试:
这样我们就可以明白上面第一部分操作的原理:
queryCommandValue:这个函数也是返回boolean类型的值。我们可以直接来做个测试:
这里的不同点就是,无参数命令和有参数命令的区别了。类似于h1标签这种,需要我们自定义标签参数的值,往往就需要使用这部分的测试方式,所以,我们也会将获取到的value放入items集合中
最后,一步就是一个通信的问题了。我们之前一篇中,聊到如果js与webView之间进行交互时,可以通过url劫持的方式来完成。我们将这个URL头进行定义,相对应这种特殊的URL头,webView会做相应的处理。
因为URL中添加参数时,都需要将值进行URL编码。所以,我们需要做一个编码的过程。
那么,至此我们提取出来的代码部分讲完了。我们回过头来再去分析一下原来的代码。
有些特殊情况或许你的考虑到:
首先,来看一下修改链接的,我们并不需要去进行状态的同步。所以,我们需要确定点击时,判断这个节点元素是否是A标签,我们可以看一下源码:
因为,我们需要修改链接,所以需要将当前这个链接的节点保留下来,方便之后的修改;同时,我们也需要向webview传递链接的name和url的信息。使用的方式——URL劫持。
之后,我们需要去考虑的是一些键位,比方说回车操作,删除操作。它们本身也不会去通知webview对其进行监听。键位的话,我们可以考虑按键时的键位code来进行特殊键位的判断,如下:
这里我们对删除键、回车键、左尖括号『<』,右尖括号『>』,做了监听,然后当用户按下这几个键时,都会调用getEditItem的方法。
状态同步的问题我们就聊那么多。之后我们来看一下我们设置的监听事件。
设置监听
直接先放上源码来让大家看一下:
直接按照顺序阐述下去吧!!
selectionchange事件,则是检测选区的变化,因为选区发送变化的时候。往往指定是焦点的变化。
每次焦点发生变化时,都需要去保存当前的range,以便于还原焦点。
focus和blur事件,其实就是需要去控制底栏按钮的可用性。因为我们的界面上面有标题栏,标题栏是不允许插入图片、插入链接、字体操作的。所以这里通过对象映射的方式,提醒webView去禁止底栏显示。
同样的,对于编辑块来说,需要监听blur事件,然后保存原来的焦点。
接下来,就是我们之前所说的点击事件的监听了。首先,点击编辑块时,需要你去保存焦点,同时同步这个位置的状态,调用getEditItem方法。
最后,需要去监听一个输入事件,因为我们需要去同步字体的数量,每当用户输入时,我们就要调用staticWords方法来同步字体的数目。
总结
最后,我们本篇文章的内容都已经分析完了。当然,你也可以细细理解我们在这里说做的所有操作,可以说这篇内容解决了我们在开发富文本编辑器时,大部分的问题。同时,也内涵了我们的思考。希望你能喜欢我们这个项目,同时帮助你的进步。