学习之後,刻意进行练习能够加深印象。
前面三天我们已经习得 DesignMode、Selection API、Clipboard API,今天我们就将这三个 Web API 一同使用,动手写一个简易的文字编辑器吧!
我们今天要做的文字编辑器没有特别的功能,只会模拟「全选」、「复制」、「剪下」、「贴上」四大功能,纯粹就是为了让大家更熟悉前面认识的 Web API 而已,未来各位想要新增其他的 feature,可以再自行发挥。
首先我们先准备一下今天的网页版面:
<div>
<button id="all">全选</button>
<button id="copy">复制</button>
<button id="cut">剪下</button>
<button id="paste">贴上</button>
</div>
<div id="editable" contenteditable="true"></div>
那上面就是我们这次的编辑器,上面是工具列的部分,下面则是文字编辑的区域,样式的话各位可以自由发挥,CSS 的部分就不额外放上来了。而为了方便,这次的可编辑范围我们使用 contenteditable
属性,各位也可以改用 iframe 搭配 DesignMode。
再来,我们先准备一些共用的变数和函式:
const editableDiv = document.querySelector("#editable");
function addClickListener(selector, callback) {
const el = document.querySelector(selector);
el.addEventListener("mousedown", function (e) {
e.preventDefault(); // 关闭 mousedown 原生事件
});
el.addEventListener("click", function (e) {
callback(e);
});
}
由於我们这次有四个按钮需要绑定事件,所以为了避免重复撰写,我们把事件监听的程序码拉出来作为共用函式,要注意的是,因为我们在点击按钮时会使得「可编辑区域」(#editable)的 focus 造成失焦,所以要特别把 mousedown
的原生事件关闭。
首先第一个按钮是「全选」,我们希望使用者在点击按钮後可以把编辑区域中的文字全部反白:
addClickListener("#all", function (e) {
const selection = window.getSelection();
selection.collapse(editableDiv, 0);
if (!editableDiv.childNodes.length) return; // 避免在没有任何文字节点时进行 extend
selection.extend(editableDiv.childNodes[0], editableDiv.textContent.length);
});
利用前面的共用函式 addClickListener
,来设定按钮,而在 Callback 中我们的步骤是先将 Selection 坍缩(collapse)在编辑区的最前头,然後透过 extend
来将 focus 移动到最後面,这样编辑区自然就会被全部反白了。
不过要注意,当编辑区没有任何文字节点时进行 extend
是会报错的,所以我们在中间有加一行防御性的判断式。
再来复制按钮要让使用者可以将目前反白的文字放进系统剪贴簿中,这样之後才能将其「贴上」。
addClickListener("#copy", function (e) {
const selection = window.getSelection();
navigator.clipboard.writeText(selection.toString());
});
复制功能就相对简单很多了,只要使用之前介绍 Selection 和 Clipboard 的基本 method 就可以实现。
剪下按钮要做的事情其实和「复制」没差多少,不过除了要将文字加进剪贴簿外,原本的文本内容需要将其移除,所以最後我们使用 Selection 的 deleteFromDocument
来处理。
addClickListener("#cut", function (e) {
const selection = window.getSelection();
navigator.clipboard.writeText(selection.toString());
selection.deleteFromDocument();
});
最後就剩贴上按钮了,也是本次练习中最复杂的功能,因为「贴上」这个动作在将剪贴簿的内容放入编辑区时,可能会有两种情境需要一并考虑:
根据以上两个情境,我们可以大致盘点出需要做的事情:
addClickListener("#paste", async function (e) {
const selection = window.getSelection();
selection.deleteFromDocument();
const offset = selection.anchorOffset;
const prefix = editableDiv.textContent.substr(0, offset);
const suffix = editableDiv.textContent.substr(offset);
const clipboardText = await navigator.clipboard.readText();
const textNode = document.createTextNode(prefix + clipboardText + suffix);
editableDiv.innerHTML = "";
editableDiv.appendChild(textNode);
selection.collapse(textNode, (prefix + clipboardText).length);
});
在整理出事项後,上面就是我们最後撰写出来的程序码了,首先执行 deleteFromDocument
,只要有反白的文字就会被删除,而且与此同时,Selection 会被自动坍缩在一个点上,这样只要取得 anchorOffset
就可以知道游标目前插入在第几个字。
然後就可以使用 substr
把需要保留的文字分割出来,最後只要加上剪贴簿的内容再放回去就行罗,最後一行则是将游标放回原本的位置。
如果想要玩玩看的话,这边是这次练习的 CodePen
不晓得大家对於这次的练习还满意吗?虽然今天做出来的功能并不是很实用,但主要还是希望和大家一起在复习前面几天学习的内容,而且其实各位时间心力的话,可以再为这个范例不断添加新功能,完成自己的作品。
另外这种文字编辑器,其实有很多 js 套件都有实现了,例如 Slate.js 就是一套很完整的工具,它使用的技术和我们今天使用的差不了多少,有兴趣的人可以再去看看。
通常这种所见即所得的编辑器会称作「富文字编辑器」(Rich Text)
上一篇介绍了The Huge One,这题因为数字太大,所以选择使用了BigInteger,当然各位...
今日题目:119. Pascal's Triangle II Given an integer ro...
SQL injection 先来简单练习 https://www.hacksplaining.com...
最近看设计相关文章时,常常会看到这四个名词,usability、accessibility、univ...
调整移动UI 今天试着调整UI到新的架构里,问题比我想像的还要多,一方面是整个架构和之前完全不一样,...