操作流程
- 开关按钮:选中单元格后右键,下拉框选择“开关按钮”,输入选择和未选择的值,点击确定,就会在该单元格下生成开关按钮,之后点击单元格就能切换值了。操作流程见下图:
- 单选框:同上选中单选框,数据各个数据值用英文逗号分割,点击确定后会在单元格中生成单选框(默认选中第一个),之后点击文字切换单元格的值。操作如下:
- 复选框:同上。
实现思路
开关按钮
1、确定数据结构。
luckysheet中一个单元格含有数据验证时,会将数据验证信息存储在dataVerification对象中,对象的key是由单元格行列坐标用“_”拼接而成,开关按钮中value1就是选中时的值,value2就是取消时的值,点击单元格切换值的时候就会读取这两个值,并且“checked”字段也会跟着切换。
2、canvas画画
在draw.js文件中,luckysheet之前画文字时是有提供一个绘画坐标的(horizonAlignPos,verticalAlignPos_text),这个变量就是参考点。先把未选中状态的图像画好,之后根据是否选中再决定是否画选中状态的图像
单选框
1、确定数据结构。
用value字段(数组)存储之前input框中输入的值,checked字段(数组,并且只有一个为true)代表对应的value是否勾选,textWidth存储的每一个值当前选项和之前所有选项文字的累加,reginAreaArr则是存储每个选项的范围(之后要判断鼠标点击在哪个选项所属的区域)。【其他字段无关紧要】
2、canvas画画
遍历checked数组后先画大圆,之后画小圆,选中状态的话就颜色是蓝色的。最后遍历value填充文字。后续圆圈的坐标就利用textWidth把前面的文字宽和圆圈宽加清楚就行。
复选框
具体操作同单选框、只是圆圈变为方块。
问题
1、如何确定鼠标点击在单选框的选项上?
首先是要确定鼠标相对于画布原点的坐标curClickCoord,然后用这个坐标与之前存储的reginAreaArr中的每一项作对比,判定选中就更新视图和相关的数据。
得到curClickCoord代码如下:
// nby 第一步存储点击坐标
let canvasClickX = event.clientX; //鼠标相对于视口左边的距离
var canvasClickY = event.clientY;
let rect = luckysheetTableContent.canvas.getBoundingClientRect(); //能得到dom相对于视口各边的距离
canvasClickX -= rect.left;
canvasClickY -= rect.top;
// nby 当前鼠标点击位置,相对于canvas
Store.curClickCoord = {x:canvasClickX,y:canvasClickY}
event.clientX和event.clientY分别是鼠标点击相对于浏览器视口的X、Y坐标 canvasDom.getBoundingClientRect().left和top则是能获取当前canvas画布相对于浏览器视口的左右距离,两者相减就是鼠标相对于画布的坐标。【1、不用offsetLeft是因为画布祖先节点有用position,这样就不是相对于body的偏移;2、不直接用offsetX是因为源码中鼠标点击没有绑定到canvas上,而是监听画布上层的div,改起来麻烦(这也是offsetX的局限性)。3、不要用pageX代替clientX,pageX是相对于文档,在画布超出有滚动条时,pageX不准】
2、canvas中咋获取文字宽的?
有measureText这个api能得到文字的宽,luckysheet中也封装了自己的方法getMeasureText用以适配不同字体下的文字宽,所以我用的luckysheet封装好的。
context.measureText(text).width;
4、除了这种点击流程,有想过其他的办法吗?
之前考虑过双击单元格后弹出radio标签,通过获取点击后的值再来改变单元格的值,这样做的目的是绕开canvas(当时不确定利用canvas是否可行),我按照这个思路写了一部分发现也挺麻烦的,并且产品经理坚持要类似直接点击radio标签的效果,所以最终就采用的画canvas的方法。利用canvas去画radio的方法实现的效果用户操作更简洁、更直观。
最后附上三个的效果图:
代码地址
由于代码量比较多,直接贴git地址。
handler.js : 用于记录鼠标点击的坐标,并且进去一个文件对坐标进行判断,从而修改这个对象的数据。
draw.js : 根据上面的对象来画canvas