react使用ant design pro时的滑动图片组件
react的滑动图片验证,是基于https://segmentfault.com/a/1190000018309458?utm_source=tag-newest做的修改,改动的主要有以下几点:
1.将css的改为less,适配ant design
2.将图片进行初次加载就执行裁剪的方法
3.适配手机的滑动事件
// index.js
/**
* @name Index
* @desc 滑动拼图验证
* @author darcrand
* @version 2019-02-26
*
* @param {String} imageUrl 图片的路径
* @param {Number} imageWidth 展示图片的宽带
* @param {Number} imageHeight 展示图片的高带
* @param {Number} fragmentSize 滑动图片的尺寸
* @param {Function} onReload 当点击'重新验证'时执行的函数
* @param {Function} onMath 匹配成功时执行的函数
* @param {Function} onError 匹配失败时执行的函数
*/ import React from "react"; import stylecss from "./index.less" const icoSuccess = require("./icons/success.png")
const icoError = require("./icons/error.png")
const icoReload = require("./icons/refresh.png")
const icoSlider = require("./icons/slider.png") const STATUS_LOADING = 0 // 还没有图片
const STATUS_READY = 1 // 图片渲染完成,可以开始滑动
const STATUS_MATCH = 2 // 图片位置匹配成功
const STATUS_ERROR = 3 // 图片位置匹配失败 const arrTips = [{ ico: icoSuccess, text: "匹配成功" }, { ico: icoError, text: "匹配失败" }] // 生成裁剪路径
function createClipPath(ctx, size = 100, styleIndex = 0) {
const styles = [
[0, 0, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 0],
[0, 0, 1, 1],
[0, 1, 0, 0],
[0, 1, 0, 1],
[0, 1, 1, 0],
[0, 1, 1, 1],
[1, 0, 0, 0],
[1, 0, 0, 1],
[1, 0, 1, 0],
[1, 0, 1, 1],
[1, 1, 0, 0],
[1, 1, 0, 1],
[1, 1, 1, 0],
[1, 1, 1, 1]
]
const style = styles[styleIndex] const r = 0.1 * size
ctx.save()
ctx.beginPath()
// left
ctx.moveTo(r, r)
ctx.lineTo(r, 0.5 * size - r)
ctx.arc(r, 0.5 * size, r, 1.5 * Math.PI, 0.5 * Math.PI, style[0])
ctx.lineTo(r, size - r)
// bottom
ctx.lineTo(0.5 * size - r, size - r)
ctx.arc(0.5 * size, size - r, r, Math.PI, 0, style[1])
ctx.lineTo(size - r, size - r)
// right
ctx.lineTo(size - r, 0.5 * size + r)
ctx.arc(size - r, 0.5 * size, r, 0.5 * Math.PI, 1.5 * Math.PI, style[2])
ctx.lineTo(size - r, r)
// top
ctx.lineTo(0.5 * size + r, r)
ctx.arc(0.5 * size, r, r, 0, Math.PI, style[3])
ctx.lineTo(r, r) ctx.clip()
ctx.closePath()
} class ImgCode extends React.Component {
static defaultProps = {
imageUrl: "",
imageWidth: 400,
imageHeight: 200,
fragmentSize: 80,
onReload: () => {},
onMatch: () => {},
onError: () => {}
} state = {
isMovable: false,
offsetX: 0, //图片截取的x
offsetY: 0, //图片截取的y
startX: 0, // 开始滑动的 x
oldX: 0,
currX: 0, // 滑块当前 x,
status: STATUS_LOADING,
showTips: false,
tipsIndex: 0
} componentDidMount() {
this.renderImage()
} componentDidUpdate(prevProps) {
// 当父组件传入新的图片后,开始渲染
if (!!this.props.imageUrl && prevProps.imageUrl !== this.props.imageUrl) {
this.renderImage()
}
} componentWillUnmount() {
this.setState = (state, callback) => {
return;
};
} renderImage = () => {
// 初始化状态
this.setState({ status: STATUS_LOADING,startX: 0, oldX: 0, currX: 0}) // 创建一个图片对象,主要用于canvas.context.drawImage()
const objImage = new Image() objImage.addEventListener("load", () => {
const { imageWidth, imageHeight, fragmentSize } = this.props // 先获取两个ctx
const ctxShadow = this.refs.shadowCanvas.getContext("2d")
const ctxFragment = this.refs.fragmentCanvas.getContext("2d") ctxShadow.clearRect(0, 0, this.props.fragmentSize, this.props.fragmentSize)
ctxFragment.clearRect(0, 0, this.props.fragmentSize, this.props.fragmentSize) // 让两个ctx拥有同样的裁剪路径(可滑动小块的轮廓)
const styleIndex = Math.floor(Math.random() * 16)
createClipPath(ctxShadow, fragmentSize, styleIndex)
createClipPath(ctxFragment, fragmentSize, styleIndex) // 随机生成裁剪图片的开始坐标
const clipX = Math.floor(fragmentSize + (imageWidth - 2 * fragmentSize) * Math.random())
const clipY = Math.floor((imageHeight - fragmentSize) * Math.random())
// 让小块绘制出被裁剪的部分
ctxFragment.drawImage(objImage, clipX, clipY, fragmentSize, fragmentSize, 0, 0, fragmentSize, fragmentSize) // 让阴影canvas带上阴影效果
ctxShadow.fillStyle = "rgba(0, 0, 0, 0.5)"
ctxShadow.fill() // 恢复画布状态
ctxShadow.restore()
ctxFragment.restore() // 设置裁剪小块的位置
this.setState({ offsetX: clipX, offsetY: clipY }) // 修改状态
this.setState({ status: STATUS_READY })
}) objImage.src = this.props.imageUrl
} onMoveStart = e => {
if (this.state.status !== STATUS_READY) {
return
} // 记录滑动开始时的绝对坐标x
this.setState({ isMovable: true, startX: e.clientX })
} onMoving = e => {
if (this.state.status !== STATUS_READY || !this.state.isMovable) {
return
}
const distance = e.clientX - this.state.startX
let currX = this.state.oldX + distance const minX = 0
const maxX = this.props.imageWidth - this.props.fragmentSize
currX = currX < minX ? 0 : currX > maxX ? maxX : currX this.setState({ currX })
} onMoveEnd = () => {
if (this.state.status !== STATUS_READY || !this.state.isMovable) {
return
}
// 将旧的固定坐标x更新
this.setState(pre => ({ isMovable: false, oldX: pre.currX })) const isMatch = Math.abs(this.state.currX - this.state.offsetX) < 5
if (isMatch) {
this.setState(pre => ({ status: STATUS_MATCH, currX: pre.offsetX }), this.onShowTips)
this.props.onMatch()
} else {
this.setState({ status: STATUS_ERROR }, () => {
this.onReset()
this.onShowTips()
})
this.props.onError()
}
} onPhoneMoveStart = e => {
if (this.state.status !== STATUS_READY) {
return
} // 记录滑动开始时的绝对坐标x
this.setState({ isMovable: true, startX: e.touches[0].pageX })
} onPhoneMoving = e => {
if (this.state.status !== STATUS_READY || !this.state.isMovable) {
return
}
const distance = e.touches[0].pageX - this.state.startX
let currX = this.state.oldX + distance const minX = 0
const maxX = this.props.imageWidth - this.props.fragmentSize
currX = currX < minX ? 0 : currX > maxX ? maxX : currX this.setState({ currX })
} onPhoneMoveEnd = () => {
if (this.state.status !== STATUS_READY || !this.state.isMovable) {
return
}
// 将旧的固定坐标x更新
this.setState(pre => ({ isMovable: false, oldX: pre.currX })) const isMatch = Math.abs(this.state.currX - this.state.offsetX) < 5
if (isMatch) {
this.setState(pre => ({ status: STATUS_MATCH, currX: pre.offsetX }), this.onShowTips)
this.props.onMatch()
} else {
this.setState({ status: STATUS_ERROR }, () => {
this.onReset()
this.onShowTips()
})
this.props.onError()
}
} onReset = () => {
const timer = setTimeout(() => {
this.setState({ oldX: 0, currX: 0, status: STATUS_READY })
clearTimeout(timer)
}, 1000)
} onReload = () => {
if (this.state.status !== STATUS_READY && this.state.status !== STATUS_MATCH) {
return
}
const ctxShadow = this.refs.shadowCanvas.getContext("2d")
const ctxFragment = this.refs.fragmentCanvas.getContext("2d") // 清空画布
ctxShadow.clearRect(0, 0, this.props.fragmentSize, this.props.fragmentSize)
ctxFragment.clearRect(0, 0, this.props.fragmentSize, this.props.fragmentSize) this.setState(
{
isMovable: false,
offsetX: 0, //图片截取的x
offsetY: 0, //图片截取的y
startX: 0, // 开始滑动的 x
oldX: 0,
currX: 0, // 滑块当前 x,
status: STATUS_LOADING
},
this.props.onReload
)
} onShowTips = () => {
if (this.state.showTips) {
return
} const tipsIndex = this.state.status === STATUS_MATCH ? 0 : 1
this.setState({ showTips: true, tipsIndex })
const timer = setTimeout(() => {
this.setState({ showTips: false })
clearTimeout(timer)
}, 2000)
} render() {
const { imageUrl, imageWidth, imageHeight, fragmentSize } = this.props
const { offsetX, offsetY, currX, showTips, tipsIndex } = this.state
const tips = arrTips[tipsIndex] const icoSlider = require("./icons/slider.png") return (
<div className={stylecss.imageCode} style={{ width: imageWidth }}>
<div className={stylecss.imageContainer} style={{ height: imageHeight, backgroundImage: `url("${imageUrl}")` }}>
<canvas
ref="shadowCanvas"
className={stylecss.canvas}
width={fragmentSize}
height={fragmentSize}
style={{ left: offsetX + "px", top: offsetY + "px" }}
/>
<canvas
ref="fragmentCanvas"
className={stylecss.canvas}
width={fragmentSize}
height={fragmentSize}
style={{ top: offsetY + "px", left: currX + "px" }}
/> <div className={showTips ? stylecss.tipsContainerActive : stylecss.tipsContainer}>
<i className={stylecss.tipsIco} style={{ backgroundImage: `url("${tips.ico}")` }} />
<span className={stylecss.tipsText}>{tips.text}</span>
</div>
</div> <div className={stylecss.reloadContainer}>
<div className={stylecss.reloadWrapper} onClick={this.onReload}>
<i className={stylecss.reloadIco} style={{ backgroundImage: `url("${icoReload}")` }} />
<span className={stylecss.reloadTips}>刷新验证</span>
</div>
</div> <div className={stylecss.sliderWrpper} onMouseMove={this.onMoving} onTouchMove={this.onPhoneMoving} onMouseLeave={this.onMoveEnd}>
<div className={stylecss.sliderBar}>按住滑块,拖动完成拼图</div>
<div
className={stylecss.sliderButton}
onTouchStart={this.onPhoneMoveStart}
onTouchEnd={this.onPhoneMoveEnd}
onMouseDown={this.onMoveStart}
onMouseUp={this.onMoveEnd}
style={{ left: currX + "px", backgroundImage: `url("${icoSlider}")` }}
/>
</div>
</div>
)
}
} export default ImgCode
样式
.imageCode {
//padding: 10px;
user-select: none;
}
.imageContainer {
position: relative;
background-color: #ddd;
}
.canvas {
position: absolute;
top:;
left:;
}
.reloadContainer {
margin: 5px 0;
}
.reloadWrapper {
display: inline-flex;
align-items: center;
cursor: pointer;
}
.reloadIco {
width: 25px;
height: 20px;
margin-right: 10px;
background: center/cover no-repeat;
}
.reloadTips {
font-size: 14px;
color: #666;
}
.sliderWrpper {
position: relative;
margin: 10px 0;
}
.sliderBar {
//padding: 10px;
font-size: 14px;
text-align: center;
color: #999;
background-color: #ddd;
}
.sliderButton {
position: absolute;
top: 50%;
left:;
width: 50px;
height: 50px;
border-radius: 25px;
transform: translateY(-50%);
cursor: pointer;
background: #fff center/80% 80% no-repeat;
box-shadow: 0 2px 10px 0 #333;
}
/* 提示信息 */
.tipsContainer,
.tipsContainerActive {
position: absolute;
top: 50%;
left: 50%;
display: flex;
align-items: center;
padding: 10px;
transform: translate(-50%, -50%);
transition: all 0.25s;
background: #fff;
border-radius: 5px;
visibility: hidden;
opacity:;
}
.tipsContainerActive {
visibility: visible;
opacity:;
}
.tipsIco {
width: 20px;
height: 20px;
margin-right: 10px;
background: center/cover no-repeat;
}
.tipsText {
color: #666;
}
使用页面
state = {
imageCodeKey: undefined, //后台返回的redis的key值
url:'', //图片路径
fileName:1 //图片名称
};
componentDidMount() {
this.fetchImageCode();
}
onReload = () => {
const {fileName} = this.state;
this.getImage(fileName);
}
getImage=(fileName)=>{
let url = `/image/`
if(fileName>=5){
fileName =1;
url = url+'1.jpg'
}else {
fileName++
url = url+fileName+'.jpg'
}
this.setState({fileName:fileName,url:url,imageCodeKey:undefined})
return url
}
// 滑动成功
sildeImageCode = () => {
const { dispatch } = this.props;
dispatch({
type: 'login/slideImageCode',
callback: res => {
const { code, data } = res;
if (code === API_RESPONSE_CODE.SUCCESS) {
this.setState({
imageCodeKey: data,
});
}
},
});
};
// 加载验证码
fetchImageCode = () => {
const {fileName} = this.state
this.setState({
imageCodeKey: undefined,
url: this.getImage(fileName)
});
};
<ImgCode
imageUrl={url}
onReload={this.onReload}
onMatch={() => {
this.sildeImageCode()
}}
/>
react使用ant design pro时的滑动图片组件的更多相关文章
- (二)React Ant Design Pro + .Net5 WebApi:前端环境搭建
首先,你需要先装一个Nodejs,这是基础哦.如果没有这方面知识的小伙伴可以在园子里搜索cnpm yarn等关键字,内容繁多,此不赘述,参考链接 一. 简介 1. Ant Design Pro v5 ...
- Ant Design Pro+Electron+electron-builder实现React应用脱离浏览器,桌面安装运行
ant-design-pro ----> version :2.3.1 由于网上Ant Design Pro+Electron的资料太少,我就贡献一点经验 最近需要讲AntD Pro项目(以 ...
- ant design pro如何实现分步表单时,返回上一步值依然被保存
首先,分步表单ant design pro支持,看官方Demo即可,那么如何实现如题,关键在于设置initialValue {getFieldDecorator('name', { initialVa ...
- ant design pro (十二)advanced UI 测试
一.概述 原文地址:https://pro.ant.design/docs/ui-test-cn UI 测试是项目研发流程中的重要一环,有效的测试用例可以梳理业务需求,保证研发的质量和进度,让工程师可 ...
- ant design pro (八)构建和发布
一.概述 原文地址:https://pro.ant.design/docs/deploy-cn 二.详细 2.1.构建 当项目开发完毕,只需要运行一行命令就可以打包你的应用: npm run buil ...
- ant design pro (六)样式
一.概述 参看地址:https://pro.ant.design/docs/style-cn 基础的 CSS 知识或查阅属性,可以参考 MDN文档. 二.详细介绍 2.1.less Ant Desig ...
- ant design pro(一)安装、目录结构、项目加载启动【原始、以及idea开发】
一.概述 1.1.脚手架概念 编程领域中的“脚手架(Scaffolding)”指的是能够快速搭建项目“骨架”的一类工具.例如大多数的React项目都有src,public,webpack配置文件等等, ...
- Ant Design Pro快速入门
在上一篇文章中,我们介绍了如何构建一个Ant Design Pro的环境. 同时讲解了如何启动服务并查看前端页面功能. 在本文中,我们将简单讲解如何在Ant Design Pro框架下实现自己的业务功 ...
- 初探ant design pro
1.增加路由子页面&配置菜单 因为ant design pro采取的是umi路由配置,所以只要在对应的文件夹下新建相关的文件夹以及文件,它会自动解析.按照如下的步骤做即可 PS.如果想要给菜单 ...
随机推荐
- BAT公司职级体系及薪水解密
BAT公司职级体系及薪水解密 互联网圈有这么一句话:百度的技术,阿里的运营,腾讯的产品.那么代表互联网三座大山的BAT,内部人才体系有什么区别呢? 先谈谈腾讯的体系. 首先是腾讯. 1.职级: 腾讯职 ...
- 使用JDBC连接MySQL数据库操作增删改查
目录 1.首先这个Myeclipse的包名以及一些实现的类(这样子写是我的习惯) 2.接下来我们创建数据库(MySQL) 3.在数据库里面添加数据 4.首先是BaseDao,这个是重中之重,注意那个数 ...
- PDF提取图片(错误纠正)
有个任务需要抽取pdf中的图片,于是找了一个例子但是有错误,仅此记录下 错误1. AttributeError: 'Document' object has no attribute 'getObje ...
- SQL-----数据库三种删除方式详解
第一种 使用delete 语句 特点: delete 属于数据库操纵语言DML,表示删除表中的数据, 删除过程是每次从表中删除一行,并把该行删除操作作为事务记录在日志中保存 可以配合事件(tran ...
- The multi-part request contained parameter data (excluding uploaded files) that exceeded the limit for maxPostSize set on the associated connector.
springboot 表单体积过大时报错: The multi-part request contained parameter data (excluding uploaded files) tha ...
- 【Luogu5349】幂(分治FFT)
[Luogu5349]幂(分治FFT) 题面 洛谷 题解 把多项式每一项拆出来考虑,于是等价于要求的只有\(\sum_{i=0}^\infty i^kr^i\). 令\(f(r)=\sum_{i=0} ...
- 大数据基础总结---MapReduce和YARN技术原理
Map Reduce和YARN技术原理 学习目标 熟悉MapReduce和YARN是什么 掌握MapReduce使用的场景及其原理 掌握MapReduce和YARN功能与架构 熟悉YARN的新特性 M ...
- Python 3 的 int 类型详解(为什么 int 不存在溢出问题?)
在以前的Python2中,整型分为int和long,也就是整型和长整型, 长整型不存在溢出问题, 即可以存放任意大小的数值,理论支持无限大数字. 因此在Python3 中,统一使用长整型,用int表示 ...
- .net 中访问config的一些方式
人所缺乏的不是才干而是志向,不是成功的能力而是勤劳的意志. 哎!好久没有写博客了,今天就分享一些比较常用的对config文件的访问一些方式. 首先 引用 using System.Configurat ...
- winfrom数据导出
/// <summary> /// 数据导出 /// </summary> /// <param name="dataGridView">< ...