react使用ant design pro时的滑动图片组件

时间:2021-08-01 02:17:20

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时的滑动图片组件的更多相关文章

  1. (二)React Ant Design Pro &plus; &period;Net5 WebApi:前端环境搭建

    首先,你需要先装一个Nodejs,这是基础哦.如果没有这方面知识的小伙伴可以在园子里搜索cnpm yarn等关键字,内容繁多,此不赘述,参考链接 一. 简介 1. Ant Design Pro v5 ...

  2. Ant Design Pro&plus;Electron&plus;electron-builder实现React应用脱离浏览器,桌面安装运行

    ant-design-pro ----> version :2.3.1 由于网上Ant Design Pro+Electron的资料太少,我就贡献一点经验   最近需要讲AntD Pro项目(以 ...

  3. ant design pro如何实现分步表单时,返回上一步值依然被保存

    首先,分步表单ant design pro支持,看官方Demo即可,那么如何实现如题,关键在于设置initialValue {getFieldDecorator('name', { initialVa ...

  4. ant design pro (十二)advanced UI 测试

    一.概述 原文地址:https://pro.ant.design/docs/ui-test-cn UI 测试是项目研发流程中的重要一环,有效的测试用例可以梳理业务需求,保证研发的质量和进度,让工程师可 ...

  5. ant design pro (八)构建和发布

    一.概述 原文地址:https://pro.ant.design/docs/deploy-cn 二.详细 2.1.构建 当项目开发完毕,只需要运行一行命令就可以打包你的应用: npm run buil ...

  6. ant design pro (六)样式

    一.概述 参看地址:https://pro.ant.design/docs/style-cn 基础的 CSS 知识或查阅属性,可以参考 MDN文档. 二.详细介绍 2.1.less Ant Desig ...

  7. ant design pro(一)安装、目录结构、项目加载启动【原始、以及idea开发】

    一.概述 1.1.脚手架概念 编程领域中的“脚手架(Scaffolding)”指的是能够快速搭建项目“骨架”的一类工具.例如大多数的React项目都有src,public,webpack配置文件等等, ...

  8. Ant Design Pro快速入门

    在上一篇文章中,我们介绍了如何构建一个Ant Design Pro的环境. 同时讲解了如何启动服务并查看前端页面功能. 在本文中,我们将简单讲解如何在Ant Design Pro框架下实现自己的业务功 ...

  9. 初探ant design pro

    1.增加路由子页面&配置菜单 因为ant design pro采取的是umi路由配置,所以只要在对应的文件夹下新建相关的文件夹以及文件,它会自动解析.按照如下的步骤做即可 PS.如果想要给菜单 ...

随机推荐

  1. ASP&period;NET MVC Razor HtmlHelper扩展和自定义控件

    先看示例代码: using System; using System.Collections.Generic; using System.Linq; using System.Web; using S ...

  2. 如何写计算机会议的rebuttal

    其实最好的教材就是实例,恰好NIPS会议会把往年所有论文的Rebuttal都贴出来...,见这里:http://papers.nips.cc/ 同时,圈内同行也总结了不少经验,下面转帖其他人的经验 = ...

  3. jsViews validates(验证)

          概述:jsViews使得前端开发过程将js代码与html分离,通过模板实现数据与html元素关联,通过绑定方法用数据填充模板,达到渲染html要素的目的.采用该方法后js文件中再也不会出现 ...

  4. ubuntu12&period;04 desktop默认无ssh支持

    sudo apt-get install ssh 安装下即可.

  5. Ueditor使用以及遇到的问题

    原来用的是Kindeditor这个编辑器,但很久没更新了,最新版是13年更新的.现在要换成百度的Ueditor, 在这里记录Ueditor的使用流程和遇到的问题. 一.下载 1.Ueditor官网 这 ...

  6. python学习第9-10天,函数。

    函数初识 为什么要使用函数? 函数最重要的目的是方便我们重复使用相同的一段程序. 将一些操作隶属于一个函数,以后你想实现相同的操作的时候,只用调用函数名就可以,而不需要重复敲所有的语句. 函数的定义与 ...

  7. Asp &period;Net Core 2&period;0 登录授权以及多用户登录

    用户登录是一个非常常见的应用场景 .net core 2.0 的登录方式发生了点变化,应该是属于是良性的变化,变得更方便,更容易扩展. 配置 打开项目中的Startup.cs文件,找到Configur ...

  8. java访问权限表

    private(私有的) 默认的(什么都不写) protected(受保护的) public(公共的 ) 同一个类中 yes   yes yes   yes 同一个包中不同类之间 no yes yes ...

  9. Python 可命名元祖

    import collections MytupleClass = collections.namedtuple('MytupleClass',['x','y','z']) obj = Mytuple ...

  10. win10常用命令和设置总结

    1.常用命令 exit:退出cmd面板; cls:清除cmd面板; 2.常用设置 2.1 services.msc 禁用:以后怎样都不会运行;手动:是打开某些用到它的程序要用到该服务时才会运行; 自动 ...