谷歌浏览器插件开发--Notes Canvas

Scroll Down

地址Notes Canvas

背景

本人经常使用Notability看一下pdf书籍,这个app可以在pdf做笔记,十分巴适。但是有些文章并不一定产pdf资料,大多通过web预览,那么是否可以在web上划线记录笔记?便有这个想法。

思路

当时想法:

  1. 先把web转成pdf,再通过canvas涂写
  2. 直接在web加上一层canvas,直接涂写
    考虑后面存在数据(目前存本地,后续将考虑存放服务),以及生成pdf可能存在弊端,就用了第二个想法。

准备

先找操作canvas第三库,轮子南造。

  1. fabric.js库,基本满足需求,画笔、箭头、文字以及擦除,简单的笔记操作足够了。
  2. jquery.js, 操作dom库,方便创建一下元素事件。

实现

操作图如下:

image.png

序号笔实现

序号笔,也是看了QQ截图有这个功能,尝试实现,它由一个方形,一个text组成的。

代码思路:

由Rect、Text、 Textbox组成一个group,当双击group时,把旧的Textbox隐藏,在相同位置创建一个可编辑可输入Textbox,当编辑结束,再把新的Textbox替换成旧的Textbox。

当页面存在数据重新展示笔记,画布初始化时,fabricjs不会自动注册group的事件。所以得通过createSerialByInitCache重新再画布上添加序号笔。

function drawSerial(canvas, mouseFrom, mouseTo, color, drawWidth, other = {}) {
  //绘制圆形
  const circle = new fabric.Rect({
    width: 18,
    height: 18,
    originX: 'center',//调整中心点的X轴坐标
    originY: 'center',//调整中心点的Y轴坐标
    fill:'#1296db',
  });
  let textNum = 1
  if (!other.num) {
    g_fc_serial_num = g_fc_serial_num + 1
    textNum = g_fc_serial_num
  } else {
    textNum = other.num
  }
  //绘制文本
  const text = new fabric.Text(textNum + '' , {
      fontSize: 8,
      fill: '#fff',
      originX: 'center',
      originY: 'center'
  })
  const textbox = new fabric.Textbox( other.boxDesc ? other.boxDesc : "双击输入内容", {
    left: 14,
    top: -5,
    fontSize: 18,
    borderColor: other.borderColor ? other.borderColor : "#FF0000",
    fill: other.fill ? other.fill : "#FF0000",
    editingBorderColor: other.editingBorderColor ? other.editingBorderColor : "#FF0000"
  });

 
  //进行组合
  const group = new fabric.Group([circle, text, textbox], {
    left: mouseFrom.x,
    top: mouseFrom.y,
    hasControls: false,
    drawType: 'serial',
  })
  // group.set('drawType', 'serial')
  group.on("mousedblclick", function(event){
    const tb = event.target.item(2)
    // const mCanvas = canvas.viewportTransform;
    // let mObject = tb.calcTransformMatrix();
    // let mTotal = fabric.util.multiplyTransformMatrices(mCanvas, mObject);
    // let trans = fabric.util.qrDecompose(mTotal);
    // console.log(trans)
    // 创建临时编辑文本对象
    let tempText = new fabric.Textbox(tb.text, {
      //... 旧文本对象需要克隆到临时的文本对象上,为了保证模拟出来的编辑框内容视觉统一。
      left: tb.group.get('left') + 25,
      top: tb.group.get('top') + 2,
      fontSize: 18,
      borderColor: tb.get('borderColor'),
      fill: tb.get('fill'),
      editingBorderColor: tb.get('editingBorderColor'),
    });

    //...

    tempText.on("editing:exited", () => {
      // 退出编辑态处理,
      // 将 text value 赋值给原始文本对象 this.item(1)
      // 将临时文本对象干掉
      tb.group.addWithUpdate(tempText)
      tb.group.remove(tb)
      canvas.remove(tempText);
      requestAnimationFrame(()=> {
        g_fc_textbox = null
      })
    });
    // textbox.enterEditing();
    // textbox.hiddenTextarea.focus();
    tb.set({
      visible: false,
    });
    // 将临时文本对象加入画布,并激活,选中进入编辑态
    canvas.add(tempText);
    canvas.setActiveObject(tempText);
    tempText.selectAll();
    tempText.enterEditing();
    requestAnimationFrame(()=> {
      g_fc_textbox = tempText
    })
  });
  return group;
}
function setSerialSortNum(canvas) {
  const elDatas = canvas.getObjects()
  let serialEls = elDatas.filter(item=> {
    return item.get('drawType') === 'serial'
  })
  g_fc_serial_num = serialEls.length
  serialEls = serialEls.sort((a, b)=> {
    return Number(a.item(1).get('text')) -  Number(b.item(1).get('text')) 
  })
  serialEls.forEach((el, index)=> {
    let text = el.item(1)
    text.set('text', (index + 1)  + '')
  })
  requestAnimationFrame(()=> {
    canvas.renderAll();
  })
}
function createSerialByInitCache(canvas) {
  const elDatas = canvas.getObjects()
  let serialEls = elDatas.filter(item=> {
    return item.get('drawType') === 'serial'
  })
  g_fc_serial_num = serialEls.length
  let gobj = null
  serialEls.forEach((el)=> {
    const text = el.item(1)
    const tb =  el.item(2)
    gobj = drawSerial(canvas, { x: el.left, y: el.top}, null, null, null,
      {
        num: text.get('text'),
        boxDesc: tb.get('text'),
        borderColor: tb.get('borderColor'),
        fill: tb.get('fill'),
        editingBorderColor: tb.get('editingBorderColor'),
      })
    canvas.remove(el)
    canvas.add(gobj)
  })
}

其他

画笔、文字等官网都有相应的demo,可以照猫画虎。

存储

目前通过localStorage存储,页面地址当作key。

遇到的坑

Manifest v3 跟 Manifest v2

插件发布时,必须 Manifest v3 版本,不然没法通过,之前开发的时候就使用v2,于是就存在很多问题。
manifest_version密钥从更改2为3

//Manifest v2
"manifest_version": 2
//Manifest v3
"manifest_version": 3

web_accessible_resources 资源问题

报错: manifest v3 Resources must be listed in the web_accessible_resources manifest key in order to be loaded by pages outside the extension.
解决:(注意 matches 这个配置一定要加上,不然访问不了)


//Manifest v2
"web_accessible_resources": [
        "js/options.js",
        "js/main.js",
        "js/injected.js"
]
//Manifest v3
"web_accessible_resources": [{
  "resources": ["images/copy.svg"],
  "matches": ["<all_urls>"],
  "extension_ids": []
}],

访问资源:

//Manifest v2
chrome.extension.getURL(params.url)
//Manifest v3
chrome.runtime.getURL(params.url)

page_action 图标配置

//Manifest v2
"browser_action": {...}
"page_action": {...}
//Manifest v3
"action": {...}

background 背景页

//Manifest v2
"background": {
 "scripts": ["js/background.js"]
}
//Manifest v3
"background": {
 "service_worker": "js/background.js"
}