初始化
This commit is contained in:
2
uni_modules/mumu-camera/changelog.md
Normal file
2
uni_modules/mumu-camera/changelog.md
Normal file
@@ -0,0 +1,2 @@
|
||||
## 1.0.0(2022-06-03)
|
||||
版本上线
|
268
uni_modules/mumu-camera/components/mumu-camera/mumu-camera.vue
Normal file
268
uni_modules/mumu-camera/components/mumu-camera/mumu-camera.vue
Normal 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>
|
1
uni_modules/mumu-camera/components/mumu-camera/track.svg
Normal file
1
uni_modules/mumu-camera/components/mumu-camera/track.svg
Normal 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 |
86
uni_modules/mumu-camera/package.json
Normal file
86
uni_modules/mumu-camera/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
137
uni_modules/mumu-camera/readme.md
Normal file
137
uni_modules/mumu-camera/readme.md
Normal 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数据 |
|
||||
|
||||
### 案例演示
|
||||

|
||||
|
||||
## 支持作者
|
||||

|
Reference in New Issue
Block a user