初始化

This commit is contained in:
2025-07-28 14:57:35 +08:00
commit 8fcffec73d
412 changed files with 73935 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
## 1.0.02022-06-03
版本上线

View File

@@ -0,0 +1,268 @@
<template>
<view>
<view class="canvasBox" v-show='isUse'>
<view class="trachBox" :style="{backgroundColor: !trackStatus ?'aliceblue' : 'khaki'}" @click='handlerTrach'
v-if='isUseTorch'>
<img src="./track.svg" class='trach'>
</view>
<view class="shutterBox" @click='hanlderShutter' hover-class="avtive">
<view class="shutter"></view>
</view>
</view>
<view class="error" v-show='!isUse'>
<view class="on1">相机权限被拒绝请尝试如下操作</view>
<view>· 刷新页面后重试</view>
<view>· 在系统中检测当前App或浏览器的相机权限是否被禁用</view>
<view>· 如果依然不能体验建议在微信中打开链接</view>
</view>
</view>
</template>
<script>
export default {
props: {
exact: {
type: String,
default: 'environment' // environment 后摄像头 user 前摄像头
},
//水印
watermark: {
type: Array,
default: () => []
}
},
data() {
return {
isUse: false,
windowWidth: 0,
windowHeight: 0,
video: null,
canvas: null,
canvas2d: null,
cameraCanvas: null,
cameraCanvas2d: null,
// 闪光灯
track: null,
isUseTorch: false,
trackStatus: false
}
},
mounted() {
if(origin.indexOf('https') === -1) throw '请在 https 环境中使用本插件。'
this.windowWidth = document.documentElement.clientWidth || document.body.clientWidth
this.windowHeight = document.documentElement.clientHeight || document.body.clientHeight
this.$nextTick(() => {
this.video = document.createElement('video')
this.video.width = this.windowWidth
this.video.height = this.windowHeight
const canvas = document.createElement('canvas')
this.canvas = canvas
canvas.id = 'canvas'
canvas.width = this.windowWidth
canvas.height = this.windowHeight
canvas.style = 'position: fixed;top: 0;left: 0;z-index:10;'
this.canvas2d = canvas.getContext('2d')
// 设置当前宽高 满屏
const canvasBox = document.querySelector('.canvasBox')
canvasBox.append(this.video)
canvasBox.append(canvas)
canvasBox.style = `width:${this.windowWidth}px;height:${this.windowHeight}px;`
const cameraCanvas = document.createElement('canvas')
this.cameraCanvas = cameraCanvas
cameraCanvas.id = 'cameraCanvas'
cameraCanvas.width = this.windowWidth
cameraCanvas.height = this.windowHeight
cameraCanvas.style = 'display:none;'
canvasBox.append(canvas)
this.cameraCanvas2d = cameraCanvas.getContext('2d')
this.openScan()
})
},
methods: {
openScan() {
let width = this.transtion(this.windowHeight)
let height = this.transtion(this.windowWidth)
const videoParam = {
audio: false,
video: {
facingMode: { exact: this.exact },
width,
height
}
}
navigator.mediaDevices.getUserMedia(videoParam).then((stream) => {
this.isUse = true
this.video.srcObject = stream
this.video.setAttribute('playsinline', true)
this.video.setAttribute('webkit-playsinline', true)
this.video.play()
this.makeWatermark(this.canvas2d)
this.track = stream.getVideoTracks()[0];
setTimeout(() => {
this.isUseTorch = this.track.getCapabilities().torch || null
}, 500)
}).catch((err) => {
console.log('设备不支持', err);
this.isUse = false
})
},
async makeWatermark(ctx2d) {
if (this.watermark.length === 0) return
for (let item of this.watermark) {
switch (item.type) {
case 'img':
const [getImgErr, { path: imgPath }] = await uni.getImageInfo({
src: item.url
});
if (getImgErr) throw item.url + '没有找到'
const img = await this.getImg(imgPath)
ctx2d.drawImage(img, item.x, item.y, item.w, item.h)
break;
case 'text':
const color = item.font_color || "#000"
const fontSize = item.font_size || 16
ctx2d.fillStyle = color
ctx2d.font = `${fontSize}px normal`
ctx2d.fillText(item.text, item.x, item.y)
break;
}
}
},
getImg(path) {
return new Promise((resolve, reject) => {
const img = new Image()
img.src = path
img.onload = (res) => {
resolve(img)
}
})
},
transtion(number) {
return number * 2
},
// 快门
async hanlderShutter() {
this.cameraCanvas2d.drawImage(this.video, 0, 0, this.windowWidth, this.windowHeight)
await this.makeWatermark(this.cameraCanvas2d)
const img = this.cameraCanvas.toDataURL('image/png')
this.$emit('success', img)
},
handlerTrach() {
console.log(12312)
this.trackStatus = !this.trackStatus
this.track.applyConstraints({
advanced: [{ torch: this.trackStatus }]
})
},
closeCamera() {
if (this.video.srcObject) {
this.video.srcObject.getTracks().forEach((track) => {
track.stop()
})
}
},
},
destroyed() {
this.closeCamera()
},
}
</script>
<style lang="scss" scoped>
page {
background-color: #333;
}
.canvasBox {
position: relative;
.shutterBox {
position: absolute;
transform: translateX(-50%);
left: 50%;
bottom: 100rpx;
background-color: aliceblue;
width: 120rpx;
height: 120rpx;
border-radius: 100rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 2rpx 4rpx 8rpx rgba(000, 000, 000, 0.3);
z-index: 100;
overflow: hidden;
.shutter {
width: 70rpx;
height: 70rpx;
border: 10rpx solid #333;
border-radius: 100rpx;
}
&.avtive::after {
content: '';
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(000, 000, 000, 0.5);
}
}
.trachBox {
position: absolute;
transform: translateX(-50%);
left: 30%;
bottom: 100rpx;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 100rpx;
box-shadow: 2rpx 4rpx 8rpx rgba(000, 000, 000, 0.3);
z-index: 30;
.trach {
width: 50rpx;
}
}
}
.error {
color: #fff;
padding: 40rpx;
font-size: 24rpx;
.on1 {
font-size: 30rpx;
}
}
</style>

View File

@@ -0,0 +1 @@
<svg t="1653986844275" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2276" width="48" height="48"><path d="M563.7 766.7H457c-9.3 0-16.8 7.5-16.8 16.8v1.2c0 9.3 7.5 16.8 16.8 16.8h106.7c9.3 0 16.8-7.5 16.8-16.8v-1.2c0-9.3-7.5-16.8-16.8-16.8zM532 829.8h-43.2c-9.4 0-17.1 7.6-17.1 17.1v0.6c0 9.4 7.6 17.1 17.1 17.1H532c9.4 0 17.1-7.6 17.1-17.1v-0.6c0-9.4-7.7-17.1-17.1-17.1zM510.4 252.6h0.6c9.4 0 17.1-7.6 17.1-17.1v-43.2c0-9.4-7.6-17.1-17.1-17.1h-0.6c-9.4 0-17.1 7.6-17.1 17.1v43.2c0 9.4 7.7 17.1 17.1 17.1zM273.1 488.5l-42.9-5.4c-9.4-1.2-17.9 5.4-19.1 14.8l-0.1 0.5c-1.2 9.4 5.4 17.9 14.8 19.1l42.9 5.4c9.4 1.2 17.9-5.4 19.1-14.8l0.1-0.6c1.2-9.3-5.4-17.9-14.8-19zM812.8 497.9c-1.2-9.4-9.7-16-19.1-14.8l-42.9 5.4c-9.4 1.2-16 9.7-14.8 19.1l0.1 0.6c1.2 9.4 9.7 16 19.1 14.8l42.9-5.4c9.4-1.2 16-9.7 14.8-19.1l-0.1-0.6zM339.1 344.6c6-7.3 5-18-2.2-24L303.6 293c-7.3-6-18-5-24 2.2l-0.4 0.4c-6 7.3-5 18 2.2 24l33.3 27.6c7.3 6 18 5 24-2.2l0.4-0.4zM754.7 295.6l-0.4-0.4c-6-7.3-16.8-8.3-24-2.2L697 320.6c-7.3 6-8.3 16.8-2.2 24l0.4 0.4c6 7.3 16.8 8.3 24 2.2l33.3-27.6c7.2-5.9 8.2-16.7 2.2-24zM510.7 302.8c-100.4 0-182.1 81.7-182.1 182.1 0 36.1 25.3 89.4 54.7 151.2 15.8 33.2 32.1 67.6 43.9 98.7v0.7h166.4l0.5 0.2c11-27.2 25.5-57.8 39.5-87.4C664.1 583.9 693 523 693 484.9c-0.2-100.4-81.9-182.1-182.3-182.1z m91.4 330.7C591.5 656 580.6 679 571 700.9H450.9c-10.9-26.1-23.7-53.2-36.3-79.7-25.2-53.1-51.3-108-51.3-136.3 0-81.3 66.1-147.4 147.4-147.4s147.4 66.1 147.4 147.4c0 30.3-28.5 90.4-56 148.6z" fill="#333333" p-id="2277"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,86 @@
{
"id": "mumu-camera",
"displayName": "h5调用摄像头拍照可加水印(水印相机),安卓浏览器中可开闪光灯。",
"version": "1.0.0",
"description": "原生js调用摄像进行拍照canvas 添加水印与导出图片",
"keywords": [
"相机",
"水印相机",
"H5调用摄像头"
],
"repository": "",
"engines": {
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无 ",
"data": "无",
"permissions": "摄像头"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "n",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "n",
"阿里": "n",
"百度": "n",
"字节跳动": "n",
"QQ": "n",
"钉钉": "n",
"快手": "n",
"飞书": "n",
"京东": "n",
"小红书": "n"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@@ -0,0 +1,137 @@
## 插件简绍
### 实现原理
> 通过 navigator.mediaDevices.getUserMedia(需要https环境) 这个api调用摄像头获取到到视频流数据。把视频流数据给到一个 video 标签就可以把视频展示在页面上了。在 video 标签的上一层创建一个 canvas 标签,用于展示水印。在创建一个隐藏的 canvas 标签,当用户点击快门的时候截取当前视频流的一帧交给隐藏的 canvas 画出来,之后再把需要添加的水印画到这个隐藏的 canvas 上,最后保存为图片。
### 使用环境
需要https环境才能使用本地测试可以在 manifest.json 中点击源码展示找到h5 ,添加:"devServer" : { "https" : true}
**请勿使用 UC浏览器 与 夸克等阿里旗下的浏览器,发现他们使用的内核都较低,无法正常获取视频流,并且都有对接视频流截取的插件,导致无法正常获取摄像头的数据。在微信中可以正常使用,推荐在微信内打开演示案例 **
需要https环境才能使用
需要https环境才能使用
需要https环境才能使用
在安卓系统上可以打开闪光灯
### 插件使用
**插件已支持 uni_modules 支持组件easycom以下代码演示的是普通使用**
``` html
<!-- HTML -->
<view>
<view class="imgBox" v-if='img'>
<image :src="img" class="img" mode="widthFix"></image>
</view>
<mumu-camera @success='handlerSuccess' :watermark='watermark' v-else></mumu-camera>
</view>
```
``` javascript
// js
components: { MumuCamera },
data() {
return {
img: '',
watermark: [
{
type: 'text',
x: 20,
y: 40,
font_color: "#fff",
font_size: 20,
text: '程序员毕业会'
},
{
type: 'text',
x: 15,
y: 100,
font_color: "#fff",
font_size: 50,
text: '你好呀'
},
]
}
},
onLoad() {
},
methods: {
handlerSuccess(img) {
this.img = img
}
}
}
```
### 相关API
##### 可传属性Props
| 参数 | 说明 | 类型 | 默认值 |
| --------- | ------------------------------------------------- | ------ | ----------- |
| exact | 选调用摄像头。environment 后摄像头 user 前摄像头 | String | environment |
| watermark | 添加水印,支持本地图片与文件。详情见下 | Array | [] |
- watermark 添加水印:
watermark 需要的是一个数组,数组中是对象。
**对象属性**
| 参数 | 类型 | 说明 |
| ---------- | ------------- | ---------------------------------- |
| type | img 或者 text | img 标识图片水印text标识文字水印 |
| x | number | 水印位置 X 轴 |
| y | number | 水印位置 Y 轴 |
| w | number | 图片的宽度,只对图片水印有效 |
| h | number | 图片的高度,只对图片水印有效 |
| url | string | 本地图片地址,只对图片水印有效 |
| font_color | 十六进制色码 | 文字颜色,只对文字水印有效 |
| font_size | number | 文字大小 |
| text | string | 添加的文字水印 |
```javascript
// 演示
watermark: [{
type: 'img',
x: 20,
y: 120,
w: 70,
h: 70,
url: '/static/logo.jpg'
},
{
type: 'text',
x: 20,
y: 40,
font_color: "#fff",
font_size: 20,
text: '程序员毕业会'
},
{
type: 'text',
x: 15,
y: 100,
font_color: "#fff",
font_size: 50,
text: '你好呀'
},
]
```
##### 事件Events
| 事件名 | 说明 | 回调参数 |
| ------- | ---------------------- | ---------------------------- |
| success | 按下快门生成的图片数据 | 图片数据返回的是base64数据 |
### 案例演示
![enter description here](https://h5plugin.mumudev.top/public/camera/qrcode.png)
## 支持作者
![支持作者](https://student.mumudev.top/wxMP.jpg)