在最近的項目中,須要經過canvas來實現一個文本編輯器,大部分場景中,其實都不須要經過canvas來實現一個編輯器。只有那種須要利用canvas的繪製功能,實現div/css沒法模擬出的文字效果,此時你須要利用canvas來實現文本編輯和渲染。此外,使用canvas實現文本編輯並非最優的,甚至是不推薦的方案,由於會存在頻繁的canvas重繪。本文介紹的是如何經過canvas來實現一個簡單的文本編輯器。css
- canvas文本編輯器的需求場景
- 如何實現一個canvas簡單的文本編輯器
- 編輯器功能優化
源碼參考地址:地址爲:github.com/fortheallli…git
原文發表在個人github: github.com/fortheallli…github
首先,仍是要強調第一點:大部分場景下,你可能不須要經過canvas來實現文本編輯器。只有兩種條件下,你須要使用canvas來實現文本編輯功能:canvas
舉一個使用canvas文本編輯器的例子,fabric.js是一個簡化canvas繪圖的工具,提供了強大的矢量圖功能,而且能夠方便的在canvas上的局部區域繪製一個個不一樣的圖案,這裏局部區域稱爲model,不一樣的model之間又能夠交互等等。app
在fabric.js常常須要對於局部的model,作一些動畫效果,若是這個model是一個文本,下面咱們簡稱文本model,用div模擬,咱們稱div文本編輯。那麼須要作兩次映射。dom
文本model渲染結果——> div文本編輯(模擬渲染結果)——>編輯 ——> 文本modal渲染結果(反重現)編輯器
這麼兩次映射,若是比較複雜,顯然是有必定的轉化工做量,這種 工做量的大小並不致命,可是這種轉化的文本編輯方式,沒法實時的去編輯,必須編輯完成後,才能再canvas中渲染出來。工具
除此以外,咱們知道大部分的文字渲染效果,經過css均可以徹底模擬出來,可是有一些文字的渲染效果是css沒法模擬的,好比:字體
這種場景下,對於複雜渲染的文字,要實現文本編輯同時還能夠邊編輯邊預覽,就必需要使用到canvas來實現文本編輯器。優化
咱們來簡單的看一下fabric.js中文本編輯的效果:
能夠訪問fabricjs官網來查看這個文本編輯的案例,按F12咱們能夠發現,該文本編輯器並非經過dom來模擬實現的,是經過canvas來直接實現文本編輯功能的。
首先經過canvas實現文本編輯,主要利用的是canvas的fillText用於繪製文字。在處理文本編輯的場景,首先要處理的是光標的問題,本文中的方法,沒有模擬出光標的閃爍效果。本文的簡易文本編輯器中,經過「|」 來實現光標的功能。
好比:
我是一隻小鳥|
複製代碼
就是一個js的字符串str = "我是一隻小鳥|",咱們用一個豎線「|」 來模擬光標
這種簡單的設定,只要咱們改變|的位置,從新繪製就實現了文本編輯器中相似的光標移動。
若是對於只有一行的文本,這裏咱們能夠保存光標的位置就是一維的,不過咱們場景的文本編輯器都是多行文本的,所以咱們須要保存光標的位置也是二維的,決定光標在哪一行,在哪一列。
this.focusIndex = [x,y]
複製代碼
保存了光標的位置以後,咱們就能夠調用fillText方法一行行的繪製文字,若是改行出現了光標,咱們就在改行的字符串中插入「|」,最後的繪製結果,就徹底模擬了文本編輯器中的光標的實現。
須要實現鼠標點擊來切換文本編輯器的光標的功能時,咱們須要測量多行文本中,每一個文字所在屏幕中的位置,計算位置的關鍵是如何計算canvas繪製的文字,每個文字的寬度和高度。
canvas中文字的寬度:能夠經過canvas的measureText來測量文字的寬度
canvas中文字的高度:在canvas中是沒有測量文字高度的方法的,不過canva中的文字跟div/css中渲染的文字,高度的實現方式是相同的,咱們能夠在div中渲染相同字體的文字,從而測量出其高度,這個高度跟在canvas中渲染出來的文字的高度是一致的。
下面是經過測量div中文字的高度,來類推canvas中文字的高度的方法:
var FontMetrics = function(family, size) {
this._family = family || (family = "Monaco, 'Courier New', Courier, monospace");
this._size = parseInt(size) || (size = 12);
// Preparing container
var line = document.createElement('div'),
body = document.body;
line.style.position = 'absolute';
line.style.whiteSpace = 'nowrap';
line.style.font = size + 'px ' + family;
body.appendChild(line);
// Now we can measure width and height of the letter
line.innerHTML = 'm'; // It doesn't matter what text goes here
this._width = line.offsetWidth;
this._height = line.offsetHeight;
// Now creating 1px sized item that will be aligned to baseline
// to calculate baseline shift
var span = document.createElement('span');
span.style.display = 'inline-block';
span.style.overflow = 'hidden';
span.style.width = '1px';
span.style.height = '1px';
line.appendChild(span);
// Baseline is important for positioning text on canvas
this._baseline = span.offsetTop + span.offsetHeight;
document.body.removeChild(line);
};
FontMetrics.prototype.getSize = function() {
return this._size;
};
複製代碼
由此咱們就知道了如何計算每一個文字的寬度和高度,從而計算出每一個文字的位置。
在canvas中繪製文本還有另外一個重要的點,就是座標轉換,如何將css座標轉化和canvas的繪圖座標進行轉化,須要理解canvas的繪圖座標和canvas的css座標之間的區別,轉化的公式以下
let ratio = canvas.width / cancas.style.width
let updateClientX = ratio * clientX
複製代碼
除了鼠標能夠點擊切換光標的位置外,還能夠經過上下左右鍵來更新光標的位置:
if(this.isFocus && e.key === 'ArrowUp'){
//邊界判斷
if(this.focusIndex[0]>0){
}
}
}
複製代碼
根據方位鍵能夠移動光標的位置,特別注意的是須要處理邊界條件,好比移動到某一行最後一列,再移動就須要換行等。
除此以外,還有回車換行Enter和刪除BackSpace鍵的處理這裏不一一舉例。
如何往canvas的文本編輯器中鍵入值,這個問題咱們須要引入一個textArea節點,改textArea的節點位置和文本光標的位置保持一致,咱們須要設置zIndex,將canvas覆蓋在textArea上:
this.textAreaLocation = () => {
//找出光標的位置,並令其絕對定位之
canvas.style.zIndex = 100;
canvas.style.position = 'absolute'
that.TextArea.style.position = 'absolute';
that.TextArea.style.zIndex = -1000;
that.TextArea.style.opacity = 0;
let y = this.focusIndex[0]
let x = this.focusIndex[1]
let cur = this.localArr[y][x]
that.TextArea.style.left = cur.x + 'px'
that.TextArea.style.top = cur.y.start + 'px';
}
複製代碼
當點擊canvas文本編輯區時:
textArea.focus()
複製代碼
當點擊文本編輯區之外的時候,
textArea.blur()
複製代碼
當輸入文字的時候,監聽textArea的input事件,從而拿到textArea輸入的值,從而渲染在canvas中。這樣就能實現英文的輸入,可是中文的鍵入沒法支持,若是須要文本編輯器能夠輸入中文,須要在textArea的input事件的基礎上,增長監聽textArea的compositionstart事件和compositionend事件。
這裏的判斷邏輯是:
若是觸發了compositionstart事件說明是一箇中文鍵入,在compositionend事件中能夠拿到完成中文輸入法後輸入的完整的值,不然就是一個英文鍵入,只須要在input中拿到英文鍵入值。
完整的代碼以下:
this.TextArea.addEventListener('compositionstart',function(e){
that.inputStatus = 'CHINESE_TYPING';
},false);
this.TextArea.addEventListener('input',function(e){
if (that.inputStatus === 'CHINESE_TYPING') {
return;
}
//處理英文輸入
},false);
this.TextArea.addEventListener('compositionend',function(e){
if(that.inputStatus === 'CHINESE_TYPING'){
//處理中文輸入
e.data .. //中文輸入的值
}
},false);
複製代碼
到此 爲止,咱們基本上能夠獲得一個完成的簡易文本編輯器。具體的效果以下:
簡易文本編輯器源碼的地址爲:github.com/fortheallli…