思路:

  本来应该全部都用canvas来实现的,但时间紧迫 写的时候只想着圆圈用li写,线用canvas,写到一半才想通,不过还好这一通下来比较顺利

  第一步:页面中的9个点用v-for循环出来li,ul设置成宽高相等的正方形。给li设置margin,保证一行只能装得下三个li,然后ul用display:flex;justify-content: space-between; align-content: space-between;给子元素排成九宫格;

  第二步:先获取九个点圆心的位置,在手指按下移动的方法中判断当前手指的坐标是否到li区域内,然后把对应位置的数字存进密码数组,调用画线的方法,把密码数组中的数字对应的点连起来,最后连到手指的位置

  第三步:在手指松开的方法中进行各种判断,判断是创建密码还是登录,判断密码长度是否小于4,有错的话颜色变红,最后再判断密码是否正确

说明:

  1、手势密码部分以组件的形式引入需要用到的页面中

  2、父组件中不传值的话就默认是创建手势密码(创建时密码长度不能小于4),需要输入两次,两次密码必须一致,如果是登录,父组件就把密码传给子组件,子组件就会根据密码判断当前输入是否正确,执行时请看控制台

不足之处:

  1、未处理创建或登录成功之后的事情(写一个emit就行了,把输入的密码传给父组件)

  2、还有一个就是canvas的高度问题,这里是用的设备高度的86%,如果九个点下面紧挨着有其他按钮的话是点不了的,因为被canvas覆盖了

演示:

父组件未传值,此时是创建手势密码

登录时,父组件穿的值为<gestureUnlock :fatherPassword="[1,2,3,4,5]"></gestureUnlock>

下面分别是密码正确和密码错误的情况

密码组件:

 <template>
<div class="gestureUnlock">
<div class="gesture">
<ul>
<li ref="selectLi" v-for="(item, index) in list" :key="item.id"
:class="{'selectedOuter': password.indexOf(index) !== -1 ? true : false,
'selectedOuter2': password.indexOf(index) !== -1 && redStyle ? true : false}">
<span :class="{'selectedInside': password.indexOf(index) !== -1 ? true : false,
'selectedInside2': password.indexOf(index) !== -1 && redStyle ? true : false}">
<!--圆心-->
<i ref="selectLiO"></i>
</span>
</li>
</ul>
</div>
<div class="canvasDiv">
<!-- <canvas id="canvasClearTop">此浏览器不支持canvas</canvas> -->
<canvas id="canvas" @touchstart="start" @touchmove="move" @touchend="end">此浏览器不支持canvas</canvas>
</div>
<div class='incorrectTip'><span v-show="tips">incorrect pattern</span></div>
</div>
</template> <script>
export default {
name: "GestureUnlock",
data () {
return {
list: [
{id:0, top: 0, left: 0, isSelected: false},
{id:1, top: 0, left: 0, isSelected: false},
{id:2, top: 0, left: 0, isSelected: false},
{id:3, top: 0, left: 0, isSelected: false},
{id:4, top: 0, left: 0, isSelected: false},
{id:5, top: 0, left: 0, isSelected: false},
{id:6, top: 0, left: 0, isSelected: false},
{id:7, top: 0, left: 0, isSelected: false},
{id:8, top: 0, left: 0, isSelected: false},
],
left: [], // 圆心x坐标
top: [], // 圆心y坐标
password: [], // 用来存储创建密码,从上到下,从左到右依次是123,456,789
cas: '', // 画笔
casClearTop:'', // 上部清除线条的画布对象
clientWidth: 0,
clientHeight: 0,
isCorrect: true, // 密码是否且是否正确
redStyle: false, // li样式是否为红色
createPassword: Array, // 这个用来存一下父组件传过来的fatherPassword,因为子组件不能直接修改父组件传过来的值
radius: Number, // 半径
tips: false // 错误提示是否显示
}
},
props: {
// 存储确认密码,变成组件后由父组件传过来,默认是空数组
fatherPassword: {
default: ()=>[], // 这个地方不能写成default: []
type: Array
}
},
created () {
// 存一下父组件传过来的fatherPassword,因为子组件不能直接修改父组件传过来的值
this.createPassword = this.fatherPassword
},
mounted() {
// 获取到的是每个方块中心i标签的位置,
for (let i = 0; i < this.$refs.selectLiO.length; i++) {
this.left.push(this.$refs.selectLiO[i].getBoundingClientRect().left)
this.top.push(this.$refs.selectLiO[i].getBoundingClientRect().top)
}
this.radius = this.$refs.selectLiO[0].getBoundingClientRect().left - this.$refs.selectLi[0].getBoundingClientRect().left
console.log('半径为:', this.radius)
console.log(this.left)
console.log(this.top)
this.clientWidth = document.documentElement.clientWidth
this.clientHeight = document.documentElement.clientHeight
console.log('设备宽高:', this.clientWidth, this.clientHeight)
this.cas = document.getElementById('canvas').getContext('2d');
document.getElementById('canvas').width = this.clientWidth;
// canvas高度为最后一个圆的圆心加半径乘以1.5,就是大于最后一行多一点
document.getElementById('canvas').height = this.top[this.top.length-1] + this.radius*1.5;
// this.casClearTop = document.getElementById('canvasClearTop').getContext('2d');
// document.getElementById('canvasClearTop').width = this.clientWidth;
// document.getElementById('canvasClearTop').height = this.top[0] - this.radius*1.5;
},
methods: {
// 手指点下
start (e) {
if(e.touches.length > 1 || e.scale && e.scale !== 1) { // 多点触碰或者缩放
console.log('这样不行', e)
} else {
console.log('start', e.touches[0].pageX , e.touches[0].pageY)
}
},
// 手指移动
move (e) {
// this.casClearTop.clearRect(0,0,200,200);
let nowLeft = e.touches[0].pageX
let nowTop = e.touches[0].pageY
for (var i = 0; i < this.left.length; i++) {
// 圆心坐标
let oLeft = this.left[i]
let oTop = this.top[i]
if((oLeft - this.radius) <= nowLeft && nowLeft <= (oLeft + this.radius) && (oTop - this.radius) <= nowTop && nowTop <= (oTop + this.radius)) {
if (this.password.length === 0 && this.password.indexOf(i) === -1) {
this.password.push(i) // 直接存进密码
} else if(this.password.indexOf(i) === -1){
console.log('连中的值:', this.password[this.password.length - 1])
let value = this.password[this.password.length - 1] // 根据此值(下标)找出对应的this.left和this.top
// value是上一个点的值,i是当前连接点的值
// 1-9 9-1、3-7 7-3、2-8 8-2、4-6 6-4
if (i === 0 && value === 8 || i === 8 && value === 0 ||
i === 2 && value === 6 || i === 6 && value === 2 ||
i === 1 && value === 7 || i === 7 && value === 1 ||
i === 3 && value === 5 || i === 5 && value === 3) {
// this.password中存的是下标
if (this.password.indexOf(4) === -1) {this.password.push(4)}
} else if(i === 2 && value === 0 || i === 0 && value === 2) { // 1-3 3-1
if (this.password.indexOf(1) === -1) {this.password.push(1)}
} else if(i === 6 && value === 8 || i === 8 && value === 6){ // 7-9 9-7
if (this.password.indexOf(7) === -1) {this.password.push(7)}
}else if(i === 0 && value === 6 || i === 6 && value === 0){ // 1-7 7-1
if (this.password.indexOf(3) === -1) {this.password.push(3)}
}else if(i === 2 && value === 8 || i === 8 && value === 2){ // 3-9 9-3
if (this.password.indexOf(5) === -1) {this.password.push(5)}
}
// 存密码
this.password.push(i)
}
}
}
this.paint(nowLeft, nowTop, true)
},
// 画线的方法
paint (nowX, nowY, color) {
// console.log('paint')
// this.casClearTop.clearRect(0,0,200,200); // 因为不是在这个canvas上画的,所以清了也没用
this.cas.clearRect(0,0,this.clientWidth,this.clientHeight); // 每次画都清空整个画布
this.cas.beginPath();
for (var i = 0; i < this.password .length; i++) {
this.cas.lineTo(this.left[this.password [i]], this.top[this.password [i]]); // 从这个开始
}
this.cas.lineTo(nowX, nowY);
if (!color) {
this.cas.strokeStyle = '#ff4b4b'
} else {
this.cas.strokeStyle = '#498bcb'
}
this.cas.lineJoin = "round"
this.cas.lineWidth = 2;
this.cas.stroke();
// 清除li内圆形区域的线条
this.password.forEach((item) => {
this.clearArcFun(this.left[item], this.top[item], this.radius)
})
},
// 清除li内的圆形区域
clearArcFun (centerX, centerY, radius) {
var stepClear = 1; //别忘记这一步
var _this = this
clearArc(centerX, centerY, radius);
function clearArc(x, y, radius){ // 圆心x,y,半径radius
var calcWidth = radius - stepClear;
var calcHeight = Math.sqrt(radius * radius - calcWidth * calcWidth);
var posX = x - calcWidth;
var posY = y - calcHeight;
var widthX = 2 * calcWidth;
var heightY = 2 * calcHeight;
if(stepClear <= radius){
_this.cas.clearRect(posX, posY, widthX, heightY);
stepClear += 1;
clearArc(x, y, radius);
}
}
},
// 手指松开
end () {
console.log('end', this.password)
if (this.createPassword.length === 0) { // 创建密码的第一次
if(this.password.length >= 4) {
this.tips = false
// 此时再调用一次paint,传undefined, undefined,避免最后一条多余的线出现
this.paint(undefined, undefined, true)
// 不变红
this.redStyle = false
this.createPassword = this.password
this.$emit('firstDown', {success: true})
// 500ms后清空样式
console.log('第一次设置密码createPassword:', this.createPassword)
console.log('第一次设置密码password:', this.password)
setTimeout(() => {
this.password = []
this.cas.clearRect(0,0,this.clientWidth,this.clientHeight);
}, 500)
} else if(this.password.length < 4 && this.password.length !== 0) {
console.log('创建密码时长度小于4')
this.tips = true
this.paint(undefined, undefined, false)
// 长度小于4样式为红色
this.redStyle = true
// 清空画布,颜色变正常,不然下次输入还是红色
setTimeout(() => {
this.password = []
this.cas.clearRect(0,0,this.clientWidth,this.clientHeight);
this.redStyle = false // 颜色变蓝,不然下次输入还是红色
}, 500)
}
} else { // 创建密码的第二次 或者 登录,不管是啥反正都是拿password和createPassword(第一次输入的密码或者父组件传过来的密码)比较
console.log('createPassword.length不为0,进入密码比较环节')
console.log('createPassword:', this.createPassword)
console.log('password:', this.password)
if (this.password.toString() === this.createPassword.toString()) {
this.tips = false
// 设置/登录成功
console.log('设置/登录成功')
this.$emit('onDrawDone', {success: true, pwd: this.password})
setTimeout(() => {
this.password = []
this.cas.clearRect(0,0,this.clientWidth,this.clientHeight);
this.redStyle = false // 没true好像就可以没有false,加上吧保险一点
}, 500)
} else if(this.password.length !== 0){ // 两次输入不一致/密码不正确 这里写this.password.length !== 0是为了防止点一下canvas也会出现输入错误的提示
this.tips = true
this.paint(undefined, undefined, false)
// 两次输入不一致/密码不正确 样式为红色
this.redStyle = true // 有true下面必得有false
console.log('失败')
// 清空画布,颜色变蓝
setTimeout(() => {
this.password = [] // 还有蓝色是因为前几个存在于那个数组,得把password清空
this.cas.clearRect(0,0,this.clientWidth,this.clientHeight);
this.redStyle = false
console.log(this.redStyle)
}, 500)
}
}
}
}
}
</script> <style lang="less" scoped>
.incorrectTip{
height: .5rem;
span{
/*line-height: .8rem;*/
color: #ff4b4b;
}
}
.gestureUnlock{
margin: 0 auto;
}
.gesture{
margin: 1.0rem auto 0;
ul{
margin: auto;
display: flex;
width: 8.88rem;
height: 8.88rem;
justify-content: space-between;
align-content: space-between;
flex-wrap: wrap;
li{
display: flex;
align-items:center;
justify-content:center;
margin: 0.45rem 0.45rem;
border-radius: 50%;
width: 1.2rem;
height: 1.2rem;
border: 0.08rem solid #e0e0e0;
/*宽度是1.2rem,边框是0.08rem,所以半径是0.68rem,1rem=37.5px,所以0.68x37.5 = 25.5px*/
span{
display: flex;
align-items:center;
justify-content:center;
width: 0.40rem;
height: 0.40rem;
border-radius: 50%;
i{
display: inline-block;
width: 1px;
height: 1px;
}
}
}
/*被选中的样式*/
.selectedOuter{
border: 0.08rem solid #498bcb;
.selectedInside{
background: #498bcb;
}
}
.selectedOuter2{
border: 0.08rem solid #ff4b4b;
.selectedInside2{
background: #ff4b4b;
}
}
}
}
.canvasDiv{
position: fixed;
top:0;
left: 0;
// background: rgba(0,0,0,0.1);
z-index: 100;
#canvasClearTop{
position: absolute;
top: 0;
left: 0;
background: rgba(255,0,0,0.2)
}
} </style>

父组件调用(创建密码):

 <template>
<!--首次登陆设置手势密码-->
<div class="createGesture">
<div class="picture">
<img :src='logoImg' alt="">
</div>
<div class="words">
<p v-if="!isShowConfirm">{{$t('createGesture.createGesture')}}</p>
<p v-if="!isShowConfirm">{{$t('createGesture.drawTips')}}.</p>
<p v-if="isShowConfirm">{{$t('createGesture.confirmGesture')}}</p>
<p v-if="isShowConfirm">{{$t('createGesture.drawTips2')}}.</p>
</div>
<!--下面这是模拟登录时传密码过去-->
<!-- <gestureUnlock @firstDown="onceDraw" @onDrawDone='fromNinePoint' :fatherPassword="[1,2,3,4,5]"></gestureUnlock> -->
<!--此页面是创建密码,需要输入两次,组件不传值,fatherPassword默认是一个空数组-->
<gestureUnlock @firstDown="onceDraw" @onDrawDone='fromNinePoint'></gestureUnlock>
<!-- @firstDown="onceDraw"是第一次输入密码的事件 @onDrawDone='fromNinePoint'第二次完成密码的事件 -->
<div class="bottom">
<p>{{$t('createGesture.bottomTips')}}.</p>
<div>
<a class="btn_text" @click="skip"> {{$t('createGesture.skip')}} </a>
</div>
</div>
</div>
</template> <script>
import gestureUnlock from '../../components/gestureUnlock'
import Vue from 'vue';
import { Grid, GridItem } from 'vant';
Vue.use(Grid).use(GridItem);
export default {
name: "createGesture",
components: {
gestureUnlock
},
data() {
return {
logoImg: require('./Zurich_logo.png'),
firstPwd: '', // 用来存创建密码时第一次输入的密码,便于和第二次比较
regOrLogin: 'reg', // 传给子组件用于判断是注册还是登陆
isShowConfirm: false, // 是否显示confirm密码
}
},
methods: {
onceDraw (e) {
if (e.success) {
console.log('第一次')
this.isShowConfirm = true
}
},
fromNinePoint (e) {
if(e.success) {
console.log('父组件:', e.pwd, '手势密码设置完成,登录')
this.send(e.pwd.join(''))
}
},
send (gesPwd) {
console.log('手势密码:', gesPwd)
this.$axios.post('http://*****/*****/****/gesturePasswordSetup', {
username: '123',
gesturePassword: gesPwd, // 手势密码
})
.then((res) => {
console.log('返回的数据:', res)
let flag = res.data.flagStr
if (flag === 'Succ') {
console.log('设置成功')
}
})
.catch((res) => {
console.log('报错:', res)
})
},
// 跳过
skip () {
this.$router.push({name: '/'})
}
}
}
</script> <style lang="less" scoped>
.createGesture{
height: 100%;
}
.picture{
padding-top: 0.533rem;
text-align: center;
img {
height: 3rem;
}
}
.words{
text-align: center;
color: #498bcb;
p:nth-child(1) {
margin: 0.267rem 0;
font-size: 0.723rem;
}
p:nth-child(2) {
font-size: 0.373rem;
}
}
.bottom{
z-index: 2000;
margin-top: .3rem /* 30/37.5 */;
width: 100%;
p{
padding: 0 0.5rem;
font-size: inherit;
}
div{
margin: 0.353rem 0 0.337rem;
}
}
</style>

vue项目中实现手势密码的更多相关文章

  1. Vue项目中添加锁屏功能

    0. 直接上 预览链接 Vue项目中添加锁屏功能 1. 实现思路 ( 1 ) 设置锁屏密码 ( 2 ) 密码存localStorage (本项目已经封装h5的sessionStorage和localS ...

  2. vue 项目中实用的小技巧

    # 在Vue 项目中引入Bootstrap 有时在vue项目中会根据需求引入Bootstrap,而Bootstrap又是依赖于jQuery的,在使用npm按照时,可能会出现一系列的错误 1.安装jQu ...

  3. 如何在VUE项目中添加ESLint

    如何在VUE项目中添加ESLint 1. 首先在项目的根目录下 新建 .eslintrc.js文件,其配置规则可以如下:(自己小整理了一份),所有的代码如下: // https://eslint.or ...

  4. 在vue项目中, mock数据

    1. 在根目录下创建 test 目录, 用来存放模拟的 json 数据, 在 test 目录下创建模拟的数据 data.json 文件 2.在build目录下的 dev-server.js的文件作如下 ...

  5. 浅谈 Axios 在 Vue 项目中的使用

    介绍 Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中. 特性 它主要有如下特性: 浏览器端发起XMLHttpRequests请求 Node端发起http ...

  6. 去除vue项目中的#及其ie9兼容性

    一.如何去除vue项目中访问地址的# vue2中在路由配置中添加mode(vue-cli创建的项目在src/router/index.js) export default new Router({ m ...

  7. vue 项目中当访问路由不存在的时候默认访问404页面

    前言: 在Vue项目中,当访问的页面路由不存在或错误时,页面显示为一片空白.然而,通常我们需要对访问url不存在或者错误的情况下添加默认的404页面,即not found页面. 一般的处理方法是: 在 ...

  8. vue项目中遇到的那些事。

    前言 有好几天没更新文章了.这段实际忙着做了一个vue的项目,从 19 天前开始,到今天刚好 20 天,独立完成. 做vue项目做这个项目一方面能为工作做一些准备,一方面也精进一下技术. 技术栈:vu ...

  9. scss/less语法以及在vue项目中的使用(转载)

    1.scss与less都是css的预处理器,首先我们的明白为什么要用scss与less,因为css只是一种标记语言,其中并没有函数变量之类的,所以当写复杂的样式时必然存在局限性,不灵活,而scss与l ...

随机推荐

  1. [CF538F]A Heap of Heaps(主席树)

    题面 题意:给你一个数组a[n],对于数组每次建立一个完全k叉树,对于每个节点,如果父节点的值比这个节点的值大,那么就是一个违规点,统计出1~n-1完全叉树下的违规点的各自的个数. 分析 注意到完全k ...

  2. weBDrriver API接口方法小记

    3.2.1 输入框(text field or textarea) 找到输入框元素:WebElement element = driver.findElement(By.id("passwd ...

  3. git 常用命令与上传步骤

      git 上传步骤: git  init  初始化Git仓库 git  add .  提交你修改的文件 git status  查看项目当中的状态(红色代表的是 未add  的文件    绿色的是已 ...

  4. 初学Java 数值运算符

    import java.util.Scanner; public class DisplayTime { public static void main(String[] args) { Scanne ...

  5. python常用函数 E

    endswith(str/tuple) 末尾元素匹配,可以传入tuple. 例子: enumerate(iterable) 可以跟踪集合元素索引,适用于迭代器. 例子: eval(str) 可以字符串 ...

  6. nodeJs express4 框架

    Express 4 框架 一.安装

  7. 第三节:MySQL的调控按钮——启动选项和系统变量

    一.命令行上使用启动选项 启动选项的通用格式 --启动选项1[=值1] --启动选项2[=值2] ... --启动选项n[=值n]    禁止TCP/IP链接 略    修改MySQL服务的默认存储引 ...

  8. java 局部变量与成员成员变量的区别

    package java04; /* 局部变量和成员变量的不同: 1.定义的位置不一样 局部变量:定义在方法内部 成员变量:在方法外部,直接写在类中 2.作用范围不一样 局部变量:只有方法中能使用,除 ...

  9. axis2获取request方法

    修改axis2的请求url-pattern 找到axis2-kernel jar包中axis2.xml配置文件的servicePath配置项,修改成与url-pattern一样的值,这样就改变了请求的 ...

  10. 【串线篇】Mybatis之模糊查询

    TeacherDao.xml sql语句:teacherName like #{name} 测试传值: teacher.setName(“%a%“):