钉钉小程序实现签名板

钉钉小程序实现签名板

前言

古茗目前已经有近万家门店了,为了对门店做规范管理,会进行巡店且输出巡店报告,此时就需要有一个老板签名的功能,证明老板认可且了解当前结果。由于我们巡店用到的是钉钉小程序,所以下面将会为大家展示如何在小程序中实现一个签名板功能。

钉钉小程序实现签名板

设计实现

为了实现签名功能,需要用到 canvas,我们翻阅钉钉 api 文档,发现支持Canvas组件,very nice,下面开始实现。(由于我们内部使用 taro 框架,以下代码均为 taro + react,我们设计稿均为 750,所以样式中数值均是实际的2倍)

创建canvas

我们先在页面中创建一个canvas画布

// SignaturePad.jsx
import { Canvas } from '@tarojs/components';

const SignaturePad = () => {
  return (
    <Canvas
      id="signature"
      canvasId="signature"
      className="canvas"
      width="343"
      height="180"
      />
  )
}

  // SignaturePad.less
.canvas {
  background: #fff;
}

钉钉小程序实现签名板

此时会发生一个神奇的现象,明明设置了 width=343 和 height=180,怎么还是钉钉默认的 300×225 ?别急,我们往下走。

调整画布

为了得到正确的展示大小,我们可以通过设置样式实现

// SignaturePad.less
.canvas {
  background: #fff;
  width: 100%;
  height: 360px;
}

钉钉小程序实现签名板

确实是起效了,那么设置widthheight属性有什么用呢,我们看下钉钉文档,可以发现这两个属性可以用来控制绘画精细度,解决在高dpr的情况下造成的绘画模糊问题。

钉钉小程序实现签名板

这里还需要注意,宽高属性需要和css中宽高属性保持相同比例,否则绘画会出现扭曲情况

初始化

画布创建完成了,接下来需要实现画笔功能,这时候就需要结合CanvasContext绘图上下文对象预设画笔属性以及后续绘图需要用到的坐标轴

// SignaturePad.jsx
let ctx = null;
let startX = 0;
let startY = 0;

const SignaturePad = () => {
	const initCanvas = () => {
    // 创建 canvas 的绘图上下文 CanvasContext 对象
    ctx = Taro.createCanvasContext('signature');
    // 设置描边颜色
    ctx.setStrokeStyle('#000000');
    // 设置线条的宽度
    ctx.setLineWidth(4);
    // 设置线条的端点样式
    ctx.setLineCap('round');
    // 设置线条的交点样式
    ctx.setLineJoin('round');
  };

  useEffect(() => {
    initCanvas();
    return () => {
      ctx = null;
    };
  }, []);

  ...
}

绘画

所有准备工作完成,然后就是如何实现绘画功能了。想要实现绘画,要对 canvas 有所了解,canvas 元素默认被网格所覆盖。通常来说网格中的一个单元相当于 canvas 元素中的一像素。栅格的起点为左上角,坐标为 (0,0) 。所有元素的位置都相对于原点来定位。所以图中蓝色方形左上角的坐标为距离左边(X 轴)x 像素,距离上边(Y 轴)y 像素,坐标为 (x, y)

钉钉小程序实现签名板

Canvas相关属性

钉钉小程序实现签名板

了解了基础知识,我们就基本知道如何实现了。通过onTouchStart确定画笔开始坐标,onTouchMove获取用户在canvas内的绘画路径,将路径上所有的点都填充上颜色。

// 是否绘画过
const isPaint = useRef(false)

const canvasStart = (e) => {
  startX = e.touches[0].x;
  startY = e.touches[0].y;
  // 开始创建一个路径
  ctx.beginPath();
};

const canvasMove = (e) => {
  if (startX !== 0 && !isPaint.current) {
    isPaint.current = true;
  }
  const { x, y } = e.touches[0];
  // 把路径移动到画布中的指定点,不创建线条
  ctx.moveTo(startX, startY);
  // 增加一个新点,然后创建一条从上次指定点到目标点的线
  ctx.lineTo(x, y);
  // 画出当前路径的边框
  ctx.stroke();
  // 将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中
  ctx.draw(true);
  startX = x;
  startY = y;
};

const canvasEnd = () => {
	ctx.closePath();
};

return (
  <Canvas
    id="signature"
    canvasId="signature"
    className="canvas"
    onTouchStart={canvasStart}
    onTouchMove={canvasMove}
    onTouchEnd={canvasEnd}
    onTouchCancel={canvasEnd}
    width="343"
    height="180"
    disableScroll // 禁止屏幕滚动以及下拉刷新
  />
)

添加操作

到这里,基础的绘画已经完成了,但是我们是需要将生成的签名保存到服务端的,所以还需要有一个确定操作。

const createImg = async () => {
  if (!isPaint.current) {
    Taro.showToast({
      title: '签名内容不能为空!',
      icon: 'none',
    });
    return false;
  }
  // 把画布内容导出成图片,返回文件路径
  const { filePath } = await ctx.toTempFilePath();
  // 这里就可以做拿到路径的后续操作了
  // ...
};

有了确定操作,假如用户签错名字了想要重写,还需要一个清除操作。

let canvasw = 0;
let canvash = 0;

// 获取 canvas 的尺寸(宽高)
const getCanvasSize = () => {
  nextTick(() => {
    // 小程序查询节点信息方法
    const query = Taro.createSelectorQuery();
    query
      .select('#signature')
      .boundingClientRect()
      .exec(([rect]) => {
        canvasw = rect.width;
        canvash = rect.height;
      });
  });
};

useEffect(() => {
  getCanvasSize();
  ...
}, []);

const clearDraw = () => {
  startX = 0;
  startY = 0;
  // 清除画布上在该矩形区域内的内容
  ctx.clearRect(0, 0, canvasw, canvash);
  ctx.draw(true);
  setIsPaint(false);
};

钉钉小程序实现签名板
到这里,一个基础的签名板已经完成了,但是还有一些可以优化的地方,下面我们将继续对它进行一些优化。

优化

撤回

清空虽然能解决用户写错的问题,但是只撤回上一笔对用户体验来说是更好的。我们可以创建一个history用于记录用户每一次绘画,然后通过getImageData获取canvas区域隐含的像素数据,将其push()history中,在触发撤回操作时,将最新一条数据pop()同时清空画布,再通过putImageDatahistory最后一条像素数据绘制到画布上,这样就能实现撤回效果。

const history = useRef([]);

const canvasEnd = async () => {
  ctx.closePath();
  const res = await ctx.getImageData({ x: 0, y: 0, width: canvasw, height: canvash });
  history.current.push(res);
};

// 撤回
const revoke = () => {
  if (!history.current.length) return;
  history.current.pop();
  if (!history.current.length) {
    ctx.clearRect(0, 0, canvasw, canvash);
    ctx.draw(true);
    return;
  }
  ctx.putImageData(history.current[history.current.length - 1]);
};

钉钉小程序实现签名板

横屏

竖屏时签字区域相对较小,只要将其切到横屏那么体验将会好非常多了。查阅钉钉文档,发现并没有提供小程序切换横竖屏的api,那么只能我们自己做一个横屏效果了。我们可以通过rotatetranslate样式,将签名版横置,再对其调整宽高。

// SignaturePad.jsx
const [full, setFull] = useState(false);

const toggleSize = () => {
  setFull(!full);
};

return (
  <View className="signature-pad-wrap">
    <View className={`signature-pad ${full ? 'full-screen' : ''}`}>
      {/* canvas */}
      ...
    </View>
  </View>
)

// SignaturePad.less
.signature-pad {
  box-sizing: border-box;
  width: 100%;
  padding: 32px 32px 30px;
  transform-origin: top left;
  transition: transform 0.3s;

  .canvas {
    width: 686px;
    height: 360px;
    background: #fff;
  }

  &.full-screen {
    width: 100vh;
    height: 100vw;
    transform: rotate(90deg) translate(0, -756px);

    .canvas {
      width: 1386px;
      height: 630px;
    }
  }
}

钉钉小程序实现签名板

然后,我们就可以看到如图效果,签名版是横置了,但是这个签名功能明显不对了。通过打印onTouchMoveevent,我们发现x,y依然是(0, 0),因为屏幕的xy轴不会变,但是我们旋转了整个签名版,所以展示出的canvas的xy轴是跟随着变形了,导致了上图情况。

钉钉小程序实现签名板钉钉小程序实现签名板

既然canvas旋转会导致xy轴变化,那么我们可以换个角度,只改变canvas的宽高,将标题按钮区域进行transform是不是就可以了

// SignaturePad.jsx
<View className={`signature-pad ${full ? 'full-screen' : ''}`}>
  <View className="signature-top">
    <View className="title">签名板</View>
    {/* 一系列按钮 */}
    </View>
  </View>
  <Canvas
    id="signature"
    canvasId="signature"
    className="canvas"
    ...
  />
</View>
          
// SignaturePad.less
.signature-pad {
  .signature-top {
    transform-origin: top left;
    transition: transform 0.3s;
  }

  .canvas {
    width: 686px;
    height: 360px;
    overflow: hidden;
    background: #fff;
  }

  &.full-screen {
    .signature-top {
      position: absolute;
      width: calc(100vh - 64px);
      transform: translate(686px, 0) rotate(90deg);
    }

    .canvas {
      width: 630px;
      height: 1386px;
    }
  }
}

钉钉小程序实现签名板

ok,可以看到,签名功能又正常了。但是,在我们点击清空的时候发现清空也坏了,这是因为我们调用的clearRect是清除画布上在该矩形区域内的内容,所以原本在初始化获取的Canvas宽高在横屏的时候实际上已经发生了变化,只要在横屏时重新获取一次组件宽高即可

const toggleSize = () => {
  setFull(!full);
  setTimeout(() => {
    getCanvasSize();
  }, 200);
};

好了,到这里已经能得到一个相对完整的签名版功能了

钉钉小程序实现签名板

总结

以上就是签名版的实现,实际上H5的实现也是类似的,只是某些部分会和小程序有所区别。整个签名板的实现基本上就是使用canvas,没有特别复杂的点,但是过程中总会遇到奇奇怪怪的问题,当你一个一个解决之后,你会发现,今天的姿势又能+1,这不就是程序员的快乐吗。感谢阅读。

最后

📚 小茗文章推荐:

关注公众号「Goodme前端团队」,获取更多干货实践,欢迎交流分享~

原文链接:https://juejin.cn/post/7329034439408500763 作者:古茗前端团队

(0)
上一篇 2024年1月29日 上午10:36
下一篇 2024年1月29日 上午10:46

相关推荐

发表回复

登录后才能评论