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.如果想要给菜单 ...
随机推荐
- [转载]3.12 UiPath存在元素Element Exists的介绍和使用
一.Element Exists的介绍 使您能够验证UI元素是否存在,即使它不可见,输出的是一个布尔值 二.Element Exists在UiPath中的使用 1.打开设计器,在设计库中新建一个Seq ...
- 深入理解C语言 - 指针使用的常见错误
在C语言中,指针的重要性不言而喻,但在很多时候指针又被认为是一把双刃剑.一方面,指针是构建数据结构和操作内存的精确而高效的工具.另一方面,它们又很容易误用,从而产生不可预知的软件bug.下面总结一下指 ...
- 使用JDBC连接MySQL数据库操作增删改查
目录 1.首先这个Myeclipse的包名以及一些实现的类(这样子写是我的习惯) 2.接下来我们创建数据库(MySQL) 3.在数据库里面添加数据 4.首先是BaseDao,这个是重中之重,注意那个数 ...
- 通过IP获取MAC地址例子(内核层)
博客地址:http://home.cnblogs.com/u/zengjianrong/ 在内核处理此流程,反而更加简单些,代码如下: #include <net/arp.h> #incl ...
- Web支持HTTPS的client(HTTP&XML-RPC)
生成Web自签名的证书(在命令行执行以下命令) keytool -genkey -keysize 2048 -validity 3650 -keyalg RSA -dname "CN=Han ...
- 【转帖】修改Windows网卡的MTU
修改Windows网卡的MTU https://blog.csdn.net/sunny05296/article/details/103438653 原创sunny05296 发布于2019-12-0 ...
- scala的应用--UDF:用户自定义函数
在window10下安装了hadoop,用ida创建maven项目. <properties> <spark.version>2.2.0</spark.version&g ...
- appium+python 多设备并行执行脚本【转】
1.ready.py文件 def getport(): aport = random.randint(4700, 4900) # 判断端口是否被占用 while getDeviceInfo.is_op ...
- 一段简单的顶部JS广告
一段简单的顶部JS广告 <SCRIPT LANGUAGE="JavaScript"> ; ; images = new Array; images[] = new Im ...
- 使用Ueditor上传图片到图片服务器(二)
上一篇主要写了前端部分如何配置ueditor的上传路径,已经jsp页面中如何使用ueditor的编辑器功能以及如何配置单独的图片上传功能. 这一篇,我分两部分:第一部分是搭建图片服务器以及配置ftp上 ...