Better

Ethan的博客,欢迎访问交流

some idea learn from konva.js

无意间了解到 konva.js 库,konva 一个 2d canvas 库,作者基于该库创建一个了付费 Polotno SDK,总结了其多年工作经验,用于快速实现在线设计站点。了解它相关 api 后,有些设计思路值得借鉴。

基础

相关 api 设计

  • stage.getPointerPosition() 得到当前光标所在坐标
  • layer.getIntersection(pos) 得到点相交的图形
  • shape.hitFunc 用于自定义命中区域,常用于增加点击区域或自定义区域,以及性能优化

相关类

  • 提供 Transformer 类用于实现 scale 和 rotate 的调整
    • enabledAnchors 数组,用于激活哪些锚点
    • rotationSnaps 数组,用户设置捕捉那些角度
    • forceUpdate
  • 提供 Tween 类用于实现对对象过渡动画
    • play
  • 提供 Animation 类实现帧动画
    • frame callback
    • start/stop

drop 实现逻辑

  • previousShape 存储之前 shape
  • 移动过程中,当新的 shape 和 previousShape 不同时,previousShape 派发 dragleave 事件,shape 派发 dragenter 事件
  • 值得借鉴的点:并不是在事件过程中去改变物体的样式,而只是手动派发事件,样式修改放在事件中去处理

性能

性能优化建议

  • 优化目标
    • 计算尽可能少
    • 绘制尽可能少
  • 相关手段
    • 优化舞台大小
    • 移动端设置 viewport,因为缩放非常影响性能
    • 设置 pixelRatio 为 1,即使在 retina 屏
    • 通过 layer 机制,将对象动静分离
    • 如果 layer 中的对象不需要响应时,则设置 listening(false)
    • 优化移动开销,拖动时将形状移动到专用层,完成时再移回原来的层
    • 使用 shape.cache(),将对象转换为图像,通过绘制图像的方式避免了根据其绘图指令组合形状的开销,对于复杂对象或组时尤为有效
    • 保持对象的顺序,通过设置 shape 的 visible 或 opacity 替换 hide 或 remove 方法
    • 设置 shape.listening(false)
    • 关闭 autoDrawEnabled,手动调用 batchDraw

PixiJS 默认使用 WebGL 进行渲染(如果不支持则降级使用 Canvas),在复杂动画方面性能更好

其他

关于撤销重做 story.history

  • canUndo
  • undo()
  • canRedo
  • redo()

替换内置 API 为自己 api:setAPI(key, func)

lock 机制的定义:关闭 draggable、contentEditable、styleEditable

关于定时保存,设置一个全局 timer 配合就节流逻辑

// write a function for throttle saving
let timeout = null;
const requestSave = () => {
  // if save is already requested - do nothing
  if (timeout) {
    return;
  }
  // schedule saving to the backend
  timeout = setTimeout(() => {
    // reset timeout
    timeout = null;
    // export the design
    const json = store.toJSON();
    // save it to the backend
    fetch('https://example.com/designs', {
      method: 'POST',
      body: JSON.stringify(json),
    });
  }, 1000);
};

// request saving operation on any changes
store.on('change', () => {
  requestSave();
});

关于字体加载:https://fonts.googleapis.com/css?family=Kavivanar

实现小预览窗口

  • 方式一:克隆后作为图片使用
  • 方式二:从最新状态生成

场景设置浮层元素

  • 组件支持设置 position 绝对定位
  • 场景计算当前需要显示的屏幕坐标
  • 使用原生 html 加载右键菜单

通过自定义锚点更新图形

function buildAnchor(x, y) {
    var anchor = new Konva.Circle({
        x: x,
        y: y,
        radius: 20,
        stroke: '#666',
        fill: '#ddd',
        strokeWidth: 2,
        draggable: true, // 复用物体移动能力
    });
    layer.add(anchor);

    // add hover styling
    anchor.on('mouseover', function () {
        document.body.style.cursor = 'pointer';
        this.strokeWidth(4);
    });
    anchor.on('mouseout', function () {
        document.body.style.cursor = 'default';
        this.strokeWidth(2);
    });

    anchor.on('dragmove', function () {
        updateDottedLines();
    });

    return anchor;
}
// function to update line points from anchors
function updateDottedLines() {
    var q = quad;
    var b = bezier;

    var quadLinePath = layer.findOne('#quadLinePath');

    // 更新路径
    quadLinePath.points([
        q.start.x(),
        q.start.y(),
        q.control.x(),
        q.control.y(),
        q.end.x(),
        q.end.y(),
    ]);
}
var quadraticLine = new Konva.Shape({
    stroke: 'red',
    strokeWidth: 4,
    // 更新修改图形
    sceneFunc: (ctx, shape) => {
        ctx.beginPath();
        ctx.moveTo(quad.start.x(), quad.start.y());
        ctx.quadraticCurveTo(
        quad.control.x(),
        quad.control.y(),
        quad.end.x(),
        quad.end.y()
        );
        ctx.fillStrokeShape(shape);
    },
});
var quadLinePath = new Konva.Line({
    dash: [10, 10, 0, 10],
    strokeWidth: 3,
    stroke: 'black',
    lineCap: 'round',
    id: 'quadLinePath',
    opacity: 0.3,
    points: [0, 0],
});
// special objects to save references to anchors
var quad = {
    start: buildAnchor(60, 30),
    control: buildAnchor(240, 110),
    end: buildAnchor(80, 160),
};


留言