如何优雅的经过cavans实现一个简单的文本编辑器


    在最近的项目中,须要经过canvas来实现一个文本编辑器,大部分场景中,其实都不须要经过canvas来实现一个编辑器。只有那种须要利用canvas的绘制功能,实现div/css没法模拟出的文字效果,此时你须要利用canvas来实现文本编辑和渲染。此外,使用canvas实现文本编辑并非最优的,甚至是不推荐的方案,由于会存在频繁的canvas重绘。本文介绍的是如何经过canvas来实现一个简单的文本编辑器。css

  • canvas文本编辑器的需求场景
  • 如何实现一个canvas简单的文本编辑器
  • 编辑器功能优化

源码参考地址:地址为:github.com/fortheallli…git

原文发表在个人github: github.com/fortheallli…github


1、canvas文本编辑器的需求场景

    首先,仍是要强调第一点:大部分场景下,你可能不须要经过canvas来实现文本编辑器。只有两种条件下,你须要使用canvas来实现文本编辑功能:canvas

  • canvas画布的图案,包含文字,须要作总体的动画以及转场等特效,须要实时编辑的场景(边编辑边渲染)
  • css没法模拟出的一些特殊文字效果,须要canvas来补充文字渲染特效

    举一个使用canvas文本编辑器的例子,fabric.js是一个简化canvas绘图的工具,提供了强大的矢量图功能,而且能够方便的在canvas上的局部区域绘制一个个不一样的图案,这里局部区域称为model,不一样的model之间又能够交互等等。app

    在fabric.js常常须要对于局部的model,作一些动画效果,若是这个model是一个文本,下面咱们简称文本model,用div模拟,咱们称div文本编辑。那么须要作两次映射。dom

文本model渲染结果——> div文本编辑(模拟渲染结果)——>编辑 ——> 文本modal渲染结果(反重现)编辑器

    这么两次映射,若是比较复杂,显然是有必定的转化工做量,这种 工做量的大小并不致命,可是这种转化的文本编辑方式,没法实时的去编辑,必须编辑完成后,才能再canvas中渲染出来。工具

    除此以外,咱们知道大部分的文字渲染效果,经过css均可以彻底模拟出来,可是有一些文字的渲染效果是css没法模拟的,好比:字体


文字1

文字2

这种场景下,对于复杂渲染的文字,要实现文本编辑同时还能够边编辑边预览,就必需要使用到canvas来实现文本编辑器。优化

咱们来简单的看一下fabric.js中文本编辑的效果:


文本编辑效果

    能够访问fabricjs官网来查看这个文本编辑的案例,按F12咱们能够发现,该文本编辑器并非经过dom来模拟实现的,是经过canvas来直接实现文本编辑功能的。

2、如何实现一个简单的文本编辑器

(1)如何模拟光标

    首先经过canvas实现文本编辑,主要利用的是canvas的fillText用于绘制文字。在处理文本编辑的场景,首先要处理的是光标的问题,本文中的方法,没有模拟出光标的闪烁效果。本文的简易文本编辑器中,经过“|” 来实现光标的功能。

    好比:

我是一只小鸟|
复制代码

    就是一个js的字符串str = "我是一只小鸟|",咱们用一个竖线“|” 来模拟光标

    这种简单的设定,只要咱们改变|的位置,从新绘制就实现了文本编辑器中相似的光标移动。

    若是对于只有一行的文本,这里咱们能够保存光标的位置就是一维的,不过咱们场景的文本编辑器都是多行文本的,所以咱们须要保存光标的位置也是二维的,决定光标在哪一行,在哪一列。

this.focusIndex = [x,y]
复制代码

    保存了光标的位置以后,咱们就能够调用fillText方法一行行的绘制文字,若是改行出现了光标,咱们就在改行的字符串中插入“|”,最后的绘制结果,就彻底模拟了文本编辑器中的光标的实现。

(2)如何处理鼠标点击切换文本编辑器的光标

    须要实现鼠标点击来切换文本编辑器的光标的功能时,咱们须要测量多行文本中,每一个文字所在屏幕中的位置,计算位置的关键是如何计算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;
    };
复制代码

    由此咱们就知道了如何计算每一个文字的宽度和高度,从而计算出每一个文字的位置。

(3)坐标转换

    在canvas中绘制文本还有另外一个重要的点,就是坐标转换,如何将css坐标转化和canvas的绘图坐标进行转化,须要理解canvas的绘图坐标和canvas的css坐标之间的区别,转化的公式以下

let ratio  = canvas.width / cancas.style.width
let updateClientX  = ratio * clientX
复制代码

(4)处理回车,空格,上下左右等按键

    除了鼠标能够点击切换光标的位置外,还能够经过上下左右键来更新光标的位置:

if(this.isFocus && e.key === 'ArrowUp'){
        //边界判断
        if(this.focusIndex[0]>0){
                
            }
        }
}
复制代码

    根据方位键能够移动光标的位置,特别注意的是须要处理边界条件,好比移动到某一行最后一列,再移动就须要换行等。

除此以外,还有回车换行Enter和删除BackSpace键的处理这里不一一举例。

(5)处理文字的键入

    如何往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);
复制代码

到此 为止,咱们基本上能够获得一个完成的简易文本编辑器。具体的效果以下:

Untitled3

简易文本编辑器源码的地址为:github.com/fortheallli…

相关文章
相关标签/搜索