参加数字公司的校招,先是留了一个大作业,要求用网页制作一个图案解锁。
经过一番奋战,总算做了出来。
放张图:
想感受一下效果么?戳这里:pattern-lock
要制作一个图案解锁,第一步要想好设计,对外开放哪些接口,如果我是一位开发者,我希望初始化的时候可以自定义行数列数,以及颜色大小等参数,如果用户不定义要有一套默认的参数,我希望可以调用方法获取用户的输入,并且提供一些额外的 API。就像买煎饼果子告诉老板不要加辣多放香菜,如果省略某些信息老板还要有自己的默认配置,最后给你你想要的煎饼果子。
JavaScript 实现类似这样:
constructor(obj) { this.row = +obj.row || 3; this.column = +obj.column || 3; this.backgroundColor = obj.backgroundColor || 'whitesmoke'; this.opacity = obj.opacity || 0.0; this.container = obj.container; this.lineColor = obj.lineColor || 'springgreen'; this.lineWidth = obj.lineWidth || 3; this.pointBackColor = obj.pointBackColor || 'white'; this.pointBorderColor = obj.pointBorderColor || 'grey'; this.radius = obj.radius || 'auto'; }
|
想好之后就是实现的问题了,图案有斜线等元素,比较复杂,所以采用 canvas 实现,以常见的三排三列为例,大致需要画出如下的图案,采取坐标轴如下(别问我为什么是左手系):
这里定义 $xunit$ 与 $yunit$ 作为单位长度,方便后续的计算,这两个值根据 canvas 的宽高以及小圆的个数计算:
$$
\begin{cases}
xunit = \frac{width}{2 \times column} \\
yunit = \frac{height}{2 \times row}
\end{cases}
$$
根据 $xunit$ 与 $yunit$ 就可以计算出第 $i$ 排第 $j$ 列的圆的圆心的坐标:
$$
point[i][j] = (2 \times (i+1) \times xunit, 2 \times (j+1) \times yunit)
$$
有了公式后进行初始化,计算 $xunit$ 与 $yunit$,使用循环把各个点的坐标存入二维数组中,同时标记节点未触摸过。JavaScript 代码:
init () { this.input = []; this.canvas = document.getElementById(this.container); this.context = this.canvas.getContext('2d'); let width = parseInt(this.canvas.getAttribute('width')); let height = parseInt(this.canvas.getAttribute('height')); this.xunit = width / (2*this.column); this.yunit = height / (2*this.row); this.radius = (this.radius === 'auto' ? Math.min(this.xunit, this.yunit) / 2 : this.radius); this.coor = []; for (let i = 0; i < this.column; i++) { this.coor[i] = []; for (let j = 0; j < this.row; j++) { this.coor[i].push({ x: this.xunit * (2*i+1), y: this.yunit * (2*j+1), visit: false }); } } this.drawCircle(); this.bindEvent(); }
|
接下来是绑定三种事件, touchstart
,touchmove
和 touchend
,这三个事件处理的事情差不多,无论哪个事件首先都要获取位置,判断是否与圆相交,这里通过
$$
\begin{cases}
recentX = \left(2 \times \left \lfloor \frac{touchX}{2 \times xunit}\right \rfloor+1 \right) \times xunit \\\\
recentY = \left( 2 \times \left \lfloor \frac{touchY}{2 \times yunit}\right \rfloor + 1 \right) \times yunit
\end{cases}
$$
算出与触摸的点最近的圆心,然后计算这两个点的距离的平方, 与半径的平方进行比较即可得知是否在圆内。每个事件都要有这个操作,所以提出来做单独的函数,JavaScript 代码:
getPosition (evt) { let touchX = evt.touches[0].clientX - evt.currentTarget.getBoundingClientRect().left; let touchY = evt.touches[0].clientY - evt.currentTarget.getBoundingClientRect().top; let indexX = Math.floor(touchX / (2*this.xunit)); let indexY = Math.floor(touchY / (2*this.yunit)); let recentX = (2*indexX+1) * this.xunit; let recentY = (2*indexY+1) * this.yunit; let hit = Math.pow(recentX-touchX, 2)+Math.pow(recentY-touchY, 2) < Math.pow(this.radius, 2); let pos = { indexX: indexX, indexY: indexY, touchX: touchX, touchY: touchY, recentX: recentX, recentY: recentY, hit: hit }; return pos; }
|
如果在圆内并且未触摸过该点,则标记该点,并将该点存入数组中。
当然,记得每次 touchmove
发生时都要清空画布重绘图案!重新绘制时需要将走过的节点两两连线,这里用一个 reduce
函数就搞定了:
self.input.reduce((prev, next) => self.drawLine(prev, next));
|
最后,整个流程最消耗资源的就是每次发生 touchmove
事件时的重绘画布了,这里需要做一下函数节流和防抖,不过我做的时候出了点问题,就先搁置了。
想了解详细的使用请移步我的 GitHub
文档信息
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
本文链接:www.snovey.com/2017/04/pattern-lock.html