原生js+h5 canvas生成图片验证码

本文介绍如何使用HTML5 Canvas和原生JS技术实现一个可点击刷新的随机验证码生成器。通过Math.random()函数生成随机颜色和字符,同时加入随机点和线增强复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实现效果


使用技术

原生js技术+html5 canvas画图

利用Math.random()函数随机生成 颜色 字符串  及障碍物

点击 验证码可变更验证码图案

 

二、使用步骤

1.html+css

代码如下(示例):

<div class="login_code_box">
                            <div class="login_code">
                                <input type="text" name="login_code" id="login_code" placeholder="请输入验证码">
                                

                            </div>
                            <canvas id="canvas" >

                            </canvas>
                        <!-- <img src="" alt="" title="看不清?单击此处刷新" class="login_code_img" > -->
                        </div>
#canvas{
    width: 130px;
    height: 45px;
    display: inline-block;
    border:  1px solid #c9c9c9;
    border-radius: 5px;
   
}

 

2.js

代码如下(示例):

//验证码生成
let canvas=document.getElementById('canvas')//画布对象

let show_num=[]//装验证码的数组
//产生随机颜色
function randomColor(){
    var r=  Math.floor( Math.random()*256);
    var g=  Math.floor( Math.random()*256);
    var b=  Math.floor( Math.random()*256);
   return  "rgb("+r+","+g+","+b+") "
}
//画布
function draw(show_num){
    let canvas_clientWidth=document.getElementById('canvas').clientWidth//画布长度
    let canvas_clientHeight=document.getElementById('canvas').clientHeight//画布高度
    let context = canvas.getContext("2d")//画布环境 创建 context 对象:
    canvas.width=canvas_clientWidth
    canvas.height=canvas_clientHeight
    let str="A,B,C,E,F,G,H,J,K,L,M,N,P,Q,R,S,T,W,X,Y,Z,1,2,3,4,5,6,7,8,9,0,q,w,e,r,t,y,u,i,o,p,a,s,d,f,g,h,j,k,l,z,x,c,v,b,n,m";
   let  astr=str.split(',');//分割字符串形成数组
   let sLength=astr.length;//数组长度
   for (let i = 0; i <= 3; i++) {
       let j=Math.floor(Math.random()*sLength);//随机索引
       let deg=Math.random()*30*Math.PI/180;//0-30随机弧度
       let text=astr[j];//随机字符
       show_num[i]=text//验证码字符数组
       let x=10+i*20//x坐标
       let y=20+Math.random()*8//y坐标
       //位移 旋转角度 颜色 文字 样式开始位置
       context.font='bold 23px 微软雅黑'
       context.translate(x, y);
       context.rotate(deg);
       context.fillStyle = randomColor();
       context.fillText(text,0,0)
       context.rotate(-deg)
       context.translate(-x,-y)
   }
   //验证码显示小点
   for(let i=0;i<=30;i++){
     context.strokeStyle=randomColor()//设置随机色用小点的颜色
     context.beginPath();//开始一条路径
     let m=Math.random()*canvas_clientWidth;
     let n=Math.random()*canvas_clientHeight;
     context.moveTo(m,n)//移动
     context.lineTo(m + 1, n + 1);//添加一个新点,然后在画布中创建从该点到最后指定点的线条
     context.stroke();//画上面定义好的路径
   }
   //验证码显示线条
   for (let i = 0; i < 8; i++) {
       context.strokeStyle=randomColor()
       context.beginPath()
       context.moveTo(Math.random()*canvas_clientWidth,Math.random()*canvas_clientHeight);
       context.lineTo(Math.random()*canvas_clientWidth,Math.random()*canvas_clientHeight)
       context.stroke()
       
   }

}
draw(show_num)
console.log(show_num);

canvas.onclick=()=>{
draw(show_num)
console.log(show_num);
}

 

 


总结

创建canvas画布,利用随机函数,创建随机颜色 。

draw()函数,用于验证码的生成。字符串分隔存入数组

循环遍历生成验证码的个数

给当前字符串一个位移及旋转角度 文字样式等

随机函数生成小点及线段 context.stroke()画好以上定义好的颜色 位移长短的路径,并显示在canvas画布上

点击canvas画布 可重新生成验证码内容

<template> <view class="webview-container"> <cover-view class="webview-wrapper"> <web-view :src="webviewPath" id="targetWebview" allow-same-origin sandbox="allow-same-origin allow-scripts" ref="webviewRef"></web-view> </cover-view> <cover-view class="bottom-action-area"> <cover-view class="capture-btn" @click="captureWebview"> <cover-view class="btn-text">立即取证</cover-view> </cover-view> </cover-view> <!-- 预览组件 --> <cover-view v-if="showPreview" class="custom-preview"> <cover-view class="preview-header"> <cover-view class="title-container"> <cover-view class="preview-title">预览界面</cover-view> </cover-view> </cover-view> <cover-image :src="screenshotPath" class="preview-image" mode="aspectFit" @error="handleImageError" ></cover-image > <cover-view class="action-buttons"> <cover-view class="action-btn cancel-btn" @click="handleAction('放弃')">放弃</cover-view> <cover-view class="action-btn confirm-btn" @click="handleAction('固化')">固化</cover-view> </cover-view> <cover-view> </cover-view> </cover-view> </view> </template> <script setup lang="ts" > import { onLoad, onReady } from '@dcloudio/uni-app'; import html2canvas from 'html2canvas'; import { nextTick, ref } from 'vue'; declare const plus: any; const webviewPath = ref(''); const ws = ref<any>(null); const screenshotPath = ref(''); const showPreview = ref(false); const platform = ref(''); const originalWebviewHeight = ref('auto'); const screenshotFilePath = ref(''); const screenshot = ref(''); const iframeLoaded = ref(false); onLoad((options: any) => { if (options.url) { webviewPath.value = decodeURIComponent(options.url); } // 获取当前平台 const systemInfo = uni.getSystemInfoSync(); platform.value = systemInfo.platform; }); onReady(() => { // #ifdef APP-PLUS const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; const currentWebview = currentPage.$getAppWebview(); setTimeout(() => { if (currentWebview.children().length > 0) { const wv = currentWebview.children()[0]; ws.value = wv; // originalWebviewHeight.value = wv.getStyle().height || 'auto'; wv.setStyle({ height: 'auto' }); } }, 1000); // #endif // #ifdef H5 const iframe = document.getElementById('targetWebview'); console.log(iframe) if (!iframe) { console.warn('未找到 iframe 元素'); return; } // 设置加载监听 // #endif }); const captureWebview = async () => { uni.showLoading({ title: '正在取证中' }) // #ifdef APP-PLUS if (!ws.value) { uni.showToast({ title: '网页未加载完成', icon: 'none' }); return; } const bitmap = new plus.nativeObj.Bitmap('screenshot') const rand = Math.floor(Math.random() * 10000) const saveUrl = '_doc/' + rand + 'order.jpg' // 绘制页面截图 ws.value.draw(bitmap, () => { // 保存图片 bitmap.save( saveUrl, { format: 'jpg', quality: 90 }, (res:any) => { // 将本地路径转换为可以展示的路径 const previewPath = plus.io.convertLocalFileSystemURL(res.target) screenshotPath.value = previewPath screenshotFilePath.value = res.target; // 保存文件路径 console.log('截图保存成功,预览路径:', previewPath) // 延迟展示,确保加载完成 setTimeout(() => { showPreview.value = true uni.hideLoading() }, 300) bitmap.clear() // 清理 bitmap 占用资源 }, (err:any) => { console.error('保存失败:', err) uni.hideLoading() uni.showToast({ title: '保存失败: ' + err.message, icon: 'none', }) bitmap.clear() } ) }, (err:any) => { console.error('截图失败:', err) uni.showToast({ title: '截图失败: ' + err.message, icon: 'none', }) uni.hideLoading() }) // #endif // #ifdef H5 const iframe = document.querySelector<HTMLIFrameElement>('#targetWebview'); console.log(iframe) if (!iframe) { throw new Error("未找到 iframe 元素"); } if (iframe.contentDocument.readyState !== 'complete') { uni.showToast({ title: '网页未加载完成', icon: 'none' }); uni.hideLoading(); return; } const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; console.log(iframeDoc) const height = Math.max( iframeDoc.body.scrollHeight, iframeDoc.documentElement.scrollHeight, iframeDoc.body.offsetHeight, iframeDoc.documentElement.offsetHeight, iframeDoc.body.clientHeight, iframeDoc.documentElement.clientHeight ); html2canvas(iframeDoc.body, { width: iframe.scrollWidth, // 使用滚动宽度 height: iframe.scrollHeight, // 使用滚动高度 scrollX: -iframe.scrollLeft, // 修正滚动偏移 scrollY: -iframe.scrollTop, // 修正滚动偏移 useCORS: true, // 启用跨域支持 allowTaint: true, // 允许污染画布 logging: true, // 开启调试日志 backgroundColor: null }).then((canvas)=>{ let base64 = canvas.toDataURL('image/png'); screenshotPath.value = base64; // console.log(screenshotPath.value) // 这里就按照chrome等新版浏览器来处理 // const a = document.createElement('a'); // a.href = base64; // a.setAttribute('download', 'pigeons-download'); // a.click(); setTimeout(() => { showPreview.value = true uni.hideLoading() }, 300) }).catch((err)=>{ console.error('截屏失败:', err); uni.showToast({ title: '截屏失败,请重试', icon: 'none' }); uni.hideLoading(); }) // #endif } const closePreview = () => { showPreview.value = false; } //上传 const uploadEvidence = () => { // #ifdef APP-PLUS // APP环境上传 plus.io.resolveLocalFileSystemURL(screenshotFilePath.value, (entry:any) => { console.log('文件存在,开始上传:', entry.name); // 继续执行上传逻辑 uni.showLoading({ title: '上传证据中...', mask: true }); // 获取文件系统路径 const filePath = entry.toLocalURL(); uni.uploadFile({ url: 'http://192.168.1.80:1592/api/upload', filePath: filePath, name: 'file', type: 'image', formData: { 'timestamp': Date.now() }, success: (uploadRes) => { uni.hideLoading(); try { const data = JSON.parse(uploadRes.data); if (data.success) { uni.showToast({ title: '证据上传成功', icon: 'none' }) } else { uni.showToast({ title: '上传失败', icon: 'none' }) console.log('上传失败') } } catch (e) { uni.showToast({ title: '解析响应失败', icon: 'none' }) } // 3秒后关闭状态提示 setTimeout(() => { closePreview(); }, 3000); }, fail: (err) => { uni.hideLoading(); console.error('上传错误详情:', err); uni.showToast({ title: '网络错误'+ err.errMsg, icon: 'none' }) setTimeout(() => { }, 3000); } }); }, (err:any) => { console.error('文件存在或路径错误:', err); uni.showToast({ title: '文件路径无效', icon: 'none' }); } ); uni.showLoading({ title: '上传证据中...', mask: true }); // #endif // #ifdef APP-H5 // H5环境上传 // if (!screenshotFilePath.value) { // uni.hideLoading(); // uni.showToast({ title: '文件路径无效', icon: 'none' }); // return; // } // 获取文件对象 // fetch(screenshotFilePath.value) // .then(res => res.blob()) // .then(blob => { // const formData = new FormData(); // formData.append('evidence', blob, `evidence-${Date.now()}.jpg`); // formData.append('timestamp', Date.now().toString()); // return fetch('http://192.168.1.80:1592/api/upload', { // method: 'POST', // body: formData // }); // }) // .then(response => response.json()) // .then(data => { // uni.hideLoading(); // if (data.success) { // uni.showToast({ title: '证据上传成功', icon: 'none' }); // } else { // uni.showToast({ title: '上传失败'+ data.message, icon: 'none' }); // } // setTimeout(() => { // closePreview(); // }, 3000); // }) // .catch(err => { // uni.hideLoading(); // uni.showToast({ title: '上传失败'+ err.message, icon: 'none' }) // setTimeout(() => { // }, 3000); // }); // #endif } const handleAction = (action: string) => { switch(action) { case '放弃': closePreview(); break; case '固化': // #ifdef APP-PLUS uni.saveImageToPhotosAlbum({ filePath: screenshotPath.value, success: () => { uni.showToast({ title: '证据已固化保存' }); // 调用上传函数 uploadEvidence(); }, fail: (err) => { uni.showToast({ title: '保存失败: ' + err.errMsg, icon: 'none' }); } }); // #endif // #ifdef APP-H5 // #endif break; } } const handleImageError = (e: any) => { console.error('图片加载错误:', e); uni.showToast({ title: '图片加载错误', icon: 'none' }); } </script> <style> .web-view { height: 100% !important; width: 100% !important; display: block !important; } .webview-container { position: relative; width: 100%; height: 100vh; overflow: hidden; } .webview-wrapper { height: calc(100vh - 120rpx); width: auto !important; overflow: visible !important; } .bottom-action-area { position: fixed; bottom: 0; left: 0; right: 0; z-index: 100; background-color: #007aff; padding: 30rpx; padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); display: flex; justify-content: center; align-items: center; } .capture-btn { width: 100%; height: 90rpx; background-color: #007aff; border-radius: 45rpx; box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.2); display: flex; align-items: center; justify-content: center; } .btn-text { color: #ffffff; font-size: 32rpx; font-weight: 500; } /* 优化预览组件 */ .custom-preview { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999; background-color: rgba(0, 0, 0, 0.9); display: flex; flex-direction: column; /* 垂直排列子元素 */ align-items: center; /* 关键修改:去掉顶部内边距,只保留底部内边距 */ padding: 0 0 40rpx 0; box-sizing: border-box; } .preview-header { width: 100%; height: 250rpx; /* 增加高度 */ background-color: #007aff; color: #ffffff; font-size: 34rpx; /* 增大字体 */ font-weight: bold; display: flex; align-items: center; justify-content: center; /* 添加圆角效果 */ border-top-left-radius: 16rpx; border-top-right-radius: 16rpx; } .preview-title { color: #ffffff; /* 白色字体 */ font-size: 28rpx; /* 字体大小适配40rpx高度 */ font-weight: bold; line-height: 40rpx; /* 与容器高度一致,实现垂直居中 */ width: 100%; text-align: center; } .preview-image { width: 100%; height: 100%; object-fit: contain; border: 1rpx solid rgba(255, 255, 255, 0.1); border-radius: 8rpx; background-color: #f5f5f5; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3); } .action-buttons { display: flex; width: 100%; /* 充满整个宽度 */ justify-content: space-between; padding: 30rpx 5%; /* 左右留5%间隙,上下30rpx内边距扩展背景区域 */ margin-top: auto; /* 借助flex布局推到最底部 */ background-color: #353336; /* 底部模块背景色 */ box-sizing: border-box; /* 确保padding影响宽度计算 */ } .action-btn { flex: 1; height: 90rpx; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; padding: 0; margin: 0 15rpx; border: none; color: #ffffff; /* 文字色适配深色背景 */ font-size: 34rpx; font-weight: 500; line-height: 1; } .cancel-btn { color: #ffffff; /* 文字颜色 */ font-size: 34rpx; font-weight: 500; /* 确保文字本身无偏移 */ line-height: 1; /* 清除行高影响 */ text-align: center; /* 辅助居中(冗余保障) */ background-color: #353336; } .confirm-btn { color: #ffffff; /* 文字颜色 */ font-size: 34rpx; font-weight: 500; /* 确保文字本身无偏移 */ line-height: 1; /* 清除行高影响 */ text-align: center; /* 辅助居中(冗余保障) */ background-color: #353336; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .bottom-action-img{ position: fixed; top: 100; left: 0; right: 0; z-index: 100; background-color: #007aff; padding: 30rpx; display: flex; justify-content: center; align-items: center; } .action-img{ width: 100%; height: 90rpx; background-color: #007aff; display: flex; } </style> 怎样将app内截图截取可视改为全部内容
最新发布
08-07
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值