添加 手势识别demo完成
This commit is contained in:
parent
a8cca89108
commit
56107508f9
@ -18,10 +18,10 @@ module.exports = {
|
||||
// 环境对象
|
||||
name: 'szxgl测试环境', // 环境名称
|
||||
script: 'npm run build:dev', // 打包命令
|
||||
host: '120.25.121.117', // 服务器地址
|
||||
host: '39.108.110.167', // 服务器地址
|
||||
port: 22, // 服务器端口号
|
||||
username: 'front', // 服务器登录用户名
|
||||
password: 'Szxgl#3210', // 服务器登录密码
|
||||
password: 'XfhdFront', // 服务器登录密码
|
||||
distPath: 'dist', // 本地打包生成目录
|
||||
webDir: '/mnt/cdn/scl/webglToy',
|
||||
// webDir: "/mnt/services/tomcat-8090-test/webapps/test/front", // test替换自己实际项目目录 服务器部署路径(不可为空或'/')
|
||||
|
||||
@ -47,6 +47,7 @@
|
||||
"@babel/core": "^7.13.14",
|
||||
"@babel/plugin-transform-runtime": "^7.13.10",
|
||||
"@babel/preset-env": "^7.13.12",
|
||||
"@handtracking.io/yoha": "^0.0.7",
|
||||
"@nutui/nutui": "^2.2.15",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
@ -56,10 +57,14 @@
|
||||
"axios": "^0.20.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"coi-serviceworker": "^0.1.6",
|
||||
"copy-webpack-plugin": "^4.4.1",
|
||||
"deploy-cli-service": "^1.2.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"glslify": "^7.1.1",
|
||||
"handtrackjs": "^0.1.5",
|
||||
"meshline": "^2.0.3",
|
||||
"qrcode": "^1.4.4",
|
||||
"stats.js": "^0.17.0",
|
||||
"style-resources-loader": "^1.3.2",
|
||||
|
||||
@ -11,7 +11,7 @@ module.exports = {
|
||||
'postcss-zindex': false
|
||||
},
|
||||
"postcss-px2rem-exclude": {
|
||||
remUnit: 37.5,//转换为rem的基准px
|
||||
remUnit: 75,//转换为rem的基准px
|
||||
remPrecision: 8, //转换精度,小数点后保留位数
|
||||
// exclude: /node_modules|folder_name/i
|
||||
},
|
||||
|
||||
BIN
src/assets/images/overlay.png
Normal file
BIN
src/assets/images/overlay.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 367 KiB |
@ -110,7 +110,7 @@ export default {
|
||||
|
||||
this.controller = this.renderer.xr.getController(0);
|
||||
this.controller.addEventListener("select", () => {
|
||||
let moved = new Date().getTime() - that.startTime > 500;
|
||||
let moved = new Date().getTime() - that.startTime > 165;
|
||||
if (!moved) {
|
||||
this.addPumpkin();
|
||||
}
|
||||
|
||||
@ -7,34 +7,24 @@
|
||||
* @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue
|
||||
-->
|
||||
<template>
|
||||
<div class="color">
|
||||
<AR></AR>
|
||||
</div>
|
||||
<div class="color">
|
||||
<AR></AR>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import AR from "@/components/ar/index.vue";
|
||||
|
||||
export default {
|
||||
name: "ar",
|
||||
components: { AR },
|
||||
mounted() {},
|
||||
methods: {
|
||||
showTips() {
|
||||
this.$weui.topTips("正在努力开发中……敬请期待", {
|
||||
duration: 1500,
|
||||
className: "custom-classname",
|
||||
callback: function () {
|
||||
// console.log('close');
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
name: "ar",
|
||||
components: { AR },
|
||||
mounted() {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="less">
|
||||
.color {
|
||||
min-height: 100vh;
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
@ -7,34 +7,24 @@
|
||||
* @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue
|
||||
-->
|
||||
<template>
|
||||
<div class="color">
|
||||
<AR></AR>
|
||||
</div>
|
||||
<div class="color">
|
||||
<AR></AR>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import AR from "@/components/ar/hit.vue";
|
||||
|
||||
export default {
|
||||
name: "arHit",
|
||||
components: { AR },
|
||||
mounted() {},
|
||||
methods: {
|
||||
showTips() {
|
||||
this.$weui.topTips("正在努力开发中……敬请期待", {
|
||||
duration: 1500,
|
||||
className: "custom-classname",
|
||||
callback: function () {
|
||||
// console.log('close');
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
name: "arHit",
|
||||
components: { AR },
|
||||
mounted() {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="less">
|
||||
.color {
|
||||
min-height: 100vh;
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
@ -1,13 +1,16 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<!-- 需要缓存的 -->
|
||||
<keep-alive>
|
||||
<router-view v-if="$route.meta.keepAlive" />
|
||||
</keep-alive>
|
||||
<!-- 不需要缓存的 -->
|
||||
<router-view v-if="!$route.meta.keepAlive" />
|
||||
</div>
|
||||
<div id="app">
|
||||
<!-- 需要缓存的 -->
|
||||
<keep-alive>
|
||||
<router-view v-if="$route.meta.keepAlive" />
|
||||
</keep-alive>
|
||||
<!-- 不需要缓存的 -->
|
||||
<router-view v-if="!$route.meta.keepAlive" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
237
src/page/index/Camera/index.vue
Normal file
237
src/page/index/Camera/index.vue
Normal file
@ -0,0 +1,237 @@
|
||||
<!--
|
||||
* @Author: your name
|
||||
* @Date: 2020-08-28 15:20:15
|
||||
* @LastEditTime: 2020-10-10 17:19:06
|
||||
* @LastEditors: Please set LastEditors
|
||||
* @Description: In User Settings Edit
|
||||
* @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue
|
||||
-->
|
||||
<template>
|
||||
<div class="camera">
|
||||
<div class="video-con">
|
||||
<div class="kuang" ref="kuang"></div>
|
||||
<video
|
||||
id="gum-local"
|
||||
class="video"
|
||||
width="100%"
|
||||
height="100%"
|
||||
autoplay
|
||||
playsinline
|
||||
ref="myVideo"
|
||||
></video>
|
||||
</div>
|
||||
|
||||
<!-- 获取权限 -->
|
||||
<button
|
||||
id="showVideo"
|
||||
class="btn"
|
||||
ref="showVideoBtn"
|
||||
@click="initVideo($event)"
|
||||
>
|
||||
开启摄像头
|
||||
</button>
|
||||
<button class="btn2" ref="takePhotoBtn" @click="takePhoto($event)">
|
||||
拍照
|
||||
</button>
|
||||
|
||||
<img ref="photo" class="photo" :src="imgsrc" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import gsap from "gsap";
|
||||
|
||||
export default {
|
||||
name: "Index",
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
imgsrc: "",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.initCanvas();
|
||||
// this.requestPermission().then((res) => {
|
||||
// if (res === true) {
|
||||
// // 权限获取成功
|
||||
// }
|
||||
// });
|
||||
},
|
||||
methods: {
|
||||
// 获取设备权限
|
||||
getDevicePermission(constraints) {
|
||||
if (navigator.mediaDevices.getUserMedia === undefined) {
|
||||
navigator.mediaDevices.getUserMedia = function (constraints) {
|
||||
var getUserMedia =
|
||||
navigator.getUserMedia ||
|
||||
navigator.webkitGetUserMedia ||
|
||||
navigator.mozGetUserMedia ||
|
||||
navigator.msGetUserMedia ||
|
||||
navigator.oGetUserMedia;
|
||||
if (!getUserMedia) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
"getUserMedia is not implemented in this browser"
|
||||
)
|
||||
);
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
getUserMedia.call(
|
||||
navigator,
|
||||
constraints,
|
||||
resolve,
|
||||
reject
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return global.navigator.mediaDevices
|
||||
.getUserMedia(constraints)
|
||||
.then((stream) => {
|
||||
if (stream) {
|
||||
stopStreamTracks(stream);
|
||||
return true;
|
||||
}
|
||||
return Promise.reject(new Error("EmptyStreamError"));
|
||||
})
|
||||
.catch((errMsg) => {
|
||||
if (errMsg && "NotAllowedError" === errMsg.name) {
|
||||
return false;
|
||||
}
|
||||
return Promise.reject(errMsg);
|
||||
});
|
||||
},
|
||||
// 开始获取权限
|
||||
requestPermission() {
|
||||
return this.getDevicePermission({ video: true, audio: true })
|
||||
.catch(() =>
|
||||
this.getDevicePermission({ video: false, audio: true })
|
||||
)
|
||||
.catch(() =>
|
||||
this.getDevicePermission({ video: true, audio: false })
|
||||
)
|
||||
.catch(() => true);
|
||||
},
|
||||
// 初始化 video
|
||||
async initVideo(e) {
|
||||
const constraints = {
|
||||
video: {
|
||||
facingMode: "user",
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
this.stream = await navigator.mediaDevices.getUserMedia(
|
||||
constraints
|
||||
);
|
||||
this.handleSuccess(this.stream, constraints);
|
||||
e.target.disabled = true;
|
||||
} catch (e) {
|
||||
this.handleError(e);
|
||||
}
|
||||
},
|
||||
handleSuccess(stream, constraints) {
|
||||
this.video = this.$refs.myVideo;
|
||||
const videoTracks = stream.getVideoTracks();
|
||||
gsap.to(this.$refs.kuang, { autoAlpha: 1 });
|
||||
gsap.to(this.$refs.showVideoBtn, { autoAlpha: 0 });
|
||||
gsap.to(this.$refs.takePhotoBtn, { autoAlpha: 1 });
|
||||
console.log("Got stream with constraints:", constraints);
|
||||
console.log(`Using video device: ${videoTracks[0].label}`);
|
||||
window.stream = stream; // make variable available to browser console
|
||||
this.video.srcObject = stream;
|
||||
},
|
||||
handleError(error) {
|
||||
console.error(error);
|
||||
},
|
||||
initCanvas() {
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
this.canvas.width = 650 * 1.2;
|
||||
this.canvas.height = 900 * 1.2;
|
||||
},
|
||||
async takePhoto() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
// this.ctx.fillStyle = "#fff";
|
||||
// this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.ctx.drawImage(
|
||||
this.$refs.myVideo,
|
||||
0,
|
||||
0,
|
||||
650,
|
||||
900,
|
||||
650 * 0.14,
|
||||
900 * 0.1,
|
||||
650,
|
||||
900
|
||||
);
|
||||
let img = await this.loadImage(
|
||||
require("@/assets/images/overlay.png")
|
||||
);
|
||||
this.ctx.drawImage(img, 0, 0, 650 * 1.2, 900 * 1.2);
|
||||
|
||||
this.imgsrc = this.canvas.toDataURL("image/jpeg");
|
||||
|
||||
gsap.to(this.$refs.photo, { autoAlpha: 1 });
|
||||
},
|
||||
loadImage(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let img = new Image();
|
||||
img.onload = (res) => {
|
||||
resolve(img);
|
||||
};
|
||||
img.onerror = (res) => {
|
||||
reject(img);
|
||||
};
|
||||
img.src = src;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="less">
|
||||
.camera {
|
||||
// min-height: 100vh;
|
||||
padding: 100px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
.kuang {
|
||||
visibility: hidden;
|
||||
.paLayout(-12.5%,-10%,120%,120%,1);
|
||||
.bg-norepeat("overlay","png");
|
||||
pointer-events: none;
|
||||
}
|
||||
.video-con {
|
||||
.prLayout(650px,900px);
|
||||
// border: 1px solid #fff;
|
||||
.video {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
// width: 700px;
|
||||
// height: 700px;
|
||||
}
|
||||
}
|
||||
.btn,
|
||||
.btn2 {
|
||||
margin: 50px;
|
||||
background-color: #fff;
|
||||
.prLayout(200px,50px);
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.btn2 {
|
||||
visibility: hidden;
|
||||
}
|
||||
.photo {
|
||||
visibility: hidden;
|
||||
.prLayout(750px,1000px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -7,34 +7,24 @@
|
||||
* @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue
|
||||
-->
|
||||
<template>
|
||||
<div class="color">
|
||||
<Color></Color>
|
||||
</div>
|
||||
<div class="color">
|
||||
<Color></Color>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import Color from "@/components/color/index.vue";
|
||||
|
||||
export default {
|
||||
name: "Index",
|
||||
components: { Color },
|
||||
mounted() {},
|
||||
methods: {
|
||||
showTips() {
|
||||
this.$weui.topTips("正在努力开发中……敬请期待", {
|
||||
duration: 1500,
|
||||
className: "custom-classname",
|
||||
callback: function () {
|
||||
// console.log('close');
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
name: "Index",
|
||||
components: { Color },
|
||||
mounted() {},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="less">
|
||||
.color {
|
||||
min-height: 100vh;
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
405
src/page/index/HandTrack/index.vue
Normal file
405
src/page/index/HandTrack/index.vue
Normal file
@ -0,0 +1,405 @@
|
||||
<template>
|
||||
<div class="hand-track">
|
||||
<video
|
||||
id="gum-local"
|
||||
class="video"
|
||||
width="100%"
|
||||
height="100%"
|
||||
autoplay
|
||||
playsinline
|
||||
webkit-playsinline
|
||||
x5-playsinline
|
||||
ref="myVideo"
|
||||
></video>
|
||||
|
||||
<!-- 获取权限 -->
|
||||
<button
|
||||
id="showVideo"
|
||||
class="btn"
|
||||
ref="showVideoBtn"
|
||||
@click="initVideo($event)"
|
||||
>
|
||||
开启摄像头
|
||||
</button>
|
||||
|
||||
<!-- info -->
|
||||
<div class="info-dialog">
|
||||
<div class="progress" ref="progress">
|
||||
当前进度: {{ progressText }}..
|
||||
</div>
|
||||
<div class="status">
|
||||
当前手势: <span>{{ handStatus }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- unsuit-dialog -->
|
||||
<div class="unsuit-dialog" ref="unsuit">请在浏览器中打开</div>
|
||||
<div class="unsuit-dialog" ref="unsuit2">您的系统版本过低</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import gsap from "gsap";
|
||||
import {
|
||||
IEngine,
|
||||
Load,
|
||||
IHandTrackingApi,
|
||||
MediaStreamErrorEnum,
|
||||
EventEnum,
|
||||
} from "@handtracking.io/yoha";
|
||||
|
||||
export default {
|
||||
name: "handTrack",
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
progressText: "0%", //进度文字
|
||||
BORDER_PADDING_FACTOR: 0.01, //边界阀值
|
||||
VIDEO_WIDTH_FACTOR: 0.8, //宽度阀值
|
||||
videoRealSize: {},
|
||||
handStatus: "未检测到手",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.video = this.$refs.myVideo;
|
||||
|
||||
this.checkUserDeviceSuitable();
|
||||
},
|
||||
methods: {
|
||||
// 检测用户手机是否兼容
|
||||
checkUserDeviceSuitable() {
|
||||
if (
|
||||
window.deviceInfo.system === "IOS" &&
|
||||
window.deviceInfo.app === "WX"
|
||||
) {
|
||||
let iosV = this.getIOSVersion();
|
||||
console.log(
|
||||
!this.versionStringCompare(iosV, 14.5),
|
||||
this.versionStringCompare(iosV, 11)
|
||||
);
|
||||
if (
|
||||
!this.versionStringCompare(iosV, 14.5) &&
|
||||
this.versionStringCompare(iosV, 11)
|
||||
) {
|
||||
gsap.to(this.$refs.unsuit, { autoAlpha: 1 });
|
||||
} else if (!this.versionStringCompare(iosV, 11)) {
|
||||
gsap.to(this.$refs.unsuit2, { autoAlpha: 1 });
|
||||
}
|
||||
}
|
||||
},
|
||||
// 版本号比较
|
||||
versionStringCompare(preVersion = "", lastVersion = "") {
|
||||
var sources = preVersion.split(".");
|
||||
var dests = lastVersion.split(".");
|
||||
var maxL = Math.max(sources.length, dests.length);
|
||||
var result = 0;
|
||||
for (let i = 0; i < maxL; i++) {
|
||||
let preValue = sources.length > i ? sources[i] : 0;
|
||||
let preNum = isNaN(Number(preValue))
|
||||
? preValue.charCodeAt()
|
||||
: Number(preValue);
|
||||
let lastValue = dests.length > i ? dests[i] : 0;
|
||||
let lastNum = isNaN(Number(lastValue))
|
||||
? lastValue.charCodeAt()
|
||||
: Number(lastValue);
|
||||
if (preNum < lastNum) {
|
||||
result = -1;
|
||||
break;
|
||||
} else if (preNum > lastNum) {
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
// 获取设备权限
|
||||
getDevicePermission(constraints) {
|
||||
if (navigator.mediaDevices.getUserMedia === undefined) {
|
||||
navigator.mediaDevices.getUserMedia = function (constraints) {
|
||||
var getUserMedia =
|
||||
navigator.getUserMedia ||
|
||||
navigator.webkitGetUserMedia ||
|
||||
navigator.mozGetUserMedia ||
|
||||
navigator.msGetUserMedia ||
|
||||
navigator.oGetUserMedia;
|
||||
if (!getUserMedia) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
"getUserMedia is not implemented in this browser"
|
||||
)
|
||||
);
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
getUserMedia.call(
|
||||
navigator,
|
||||
constraints,
|
||||
resolve,
|
||||
reject
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return global.navigator.mediaDevices
|
||||
.getUserMedia(constraints)
|
||||
.then((stream) => {
|
||||
if (stream) {
|
||||
stopStreamTracks(stream);
|
||||
return true;
|
||||
}
|
||||
return Promise.reject(new Error("EmptyStreamError"));
|
||||
})
|
||||
.catch((errMsg) => {
|
||||
if (errMsg && "NotAllowedError" === errMsg.name) {
|
||||
return false;
|
||||
}
|
||||
return Promise.reject(errMsg);
|
||||
});
|
||||
},
|
||||
// 开始获取权限
|
||||
requestPermission() {
|
||||
return this.getDevicePermission({ video: true, audio: true })
|
||||
.catch(() =>
|
||||
this.getDevicePermission({ video: false, audio: true })
|
||||
)
|
||||
.catch(() =>
|
||||
this.getDevicePermission({ video: true, audio: false })
|
||||
)
|
||||
.catch(() => true);
|
||||
},
|
||||
// 获取摄像头设备id
|
||||
async getBackCameraDeviceId() {
|
||||
let source = new Array();
|
||||
let devices = await navigator.mediaDevices.enumerateDevices();
|
||||
if (devices.length > 0) {
|
||||
devices.forEach(function (device) {
|
||||
if (device.kind == "videoinput") {
|
||||
source.push(device);
|
||||
}
|
||||
});
|
||||
}
|
||||
return source[source.length - 1].deviceId;
|
||||
},
|
||||
// 初始化 video
|
||||
async initVideo(e) {
|
||||
const constraints = {
|
||||
video: {
|
||||
deviceId: { exact: await this.getBackCameraDeviceId() },
|
||||
facingMode: "environment",
|
||||
},
|
||||
// video: {
|
||||
// facingMode: { exact: "environment" },
|
||||
// width: { min: 375, ideal: 375, max: 750 },
|
||||
// height: { min: 590, ideal: 590, max: 1624 },
|
||||
// },
|
||||
};
|
||||
|
||||
this.video.play();
|
||||
|
||||
try {
|
||||
this.stream = await navigator.mediaDevices.getUserMedia(
|
||||
constraints
|
||||
);
|
||||
|
||||
gsap.to(e.target, { autoAlpha: 0 });
|
||||
e.target.disabled = true;
|
||||
|
||||
// 处理成功回调
|
||||
this.handleSuccess(this.stream, constraints);
|
||||
} catch (e) {
|
||||
this.handleError(e);
|
||||
}
|
||||
},
|
||||
// 处理stream获取成功
|
||||
async handleSuccess(stream, constraints) {
|
||||
const videoTracks = stream.getVideoTracks();
|
||||
this.stream = stream; // make variable available to browser console
|
||||
let streamSize = this.GetStreamDimensions(stream);
|
||||
let handledSize = this.ScaleResolutionToWidth(
|
||||
streamSize,
|
||||
window.innerWidth
|
||||
);
|
||||
|
||||
this.videoRealSize = handledSize;
|
||||
|
||||
console.log("Got stream with constraints:", constraints);
|
||||
console.log(`Using video device: ${videoTracks[0].label}`);
|
||||
console.log("Got stream size:", streamSize);
|
||||
console.log(
|
||||
"Got streamHandled size:",
|
||||
window.innerWidth,
|
||||
handledSize
|
||||
);
|
||||
|
||||
this.responseVideo(handledSize);
|
||||
this.video.srcObject = stream;
|
||||
|
||||
// 开始识别
|
||||
this.startDraw();
|
||||
},
|
||||
// 处理stream获取错误
|
||||
handleError(error) {
|
||||
console.error(error);
|
||||
},
|
||||
// 视频设置
|
||||
responseVideo(size) {
|
||||
this.video.width = size.width;
|
||||
this.video.height = size.height;
|
||||
},
|
||||
// 获得stream大小
|
||||
GetStreamDimensions(stream) {
|
||||
return {
|
||||
width: stream.getVideoTracks()[0].getSettings().width,
|
||||
height: stream.getVideoTracks()[0].getSettings().height,
|
||||
};
|
||||
},
|
||||
// 等比缩放视频
|
||||
ScaleResolutionToWidth(resolution, width) {
|
||||
const cw = resolution.width;
|
||||
const ch = resolution.height;
|
||||
const tw = width;
|
||||
|
||||
return {
|
||||
width: tw,
|
||||
height: ch / (cw / tw),
|
||||
};
|
||||
},
|
||||
// 创建手势识别引擎
|
||||
async CreateEngine() {
|
||||
let API = IHandTrackingApi;
|
||||
if (API) {
|
||||
return API.CreateEngine();
|
||||
}
|
||||
API = await Load("./yoha");
|
||||
return API.CreateEngine();
|
||||
},
|
||||
// 开始识别
|
||||
async startDraw() {
|
||||
this.engine = await this.CreateEngine();
|
||||
await this.engine.DownloadModel((progress) => {
|
||||
this.progressText = `${Math.round(progress * 100)}%`;
|
||||
});
|
||||
|
||||
this.progressText = "引擎准备中…";
|
||||
this.engine.Configure({
|
||||
// Webcam video is usually flipped so we want the coordinates to be flipped as well.
|
||||
flipX: false,
|
||||
// Crop away a small area at the border to prevent the user to move out of view
|
||||
// when reaching for border areas on the canvas.
|
||||
padding: this.BORDER_PADDING_FACTOR,
|
||||
});
|
||||
|
||||
this.engine.SetUpCustomTrackSource(this.video);
|
||||
// await this.engine.SetUpCameraTrackSource();
|
||||
|
||||
// ios需要预热
|
||||
if (window.deviceInfo.system === "IOS") {
|
||||
await this.engine.Warmup();
|
||||
}
|
||||
|
||||
this.progressText = "引擎已就绪";
|
||||
gsap.to(this.$refs.progress, {
|
||||
autoAlpha: 0,
|
||||
delay: 0.5,
|
||||
onComplete: () => {
|
||||
this.$refs.progress.style.display = "none";
|
||||
},
|
||||
});
|
||||
|
||||
this.engine.Start((e) => {
|
||||
if (e.type === EventEnum.RESULT) {
|
||||
if (e.poses.fist) {
|
||||
this.handStatus = "握拳";
|
||||
// document.getElementById("status").innerText = "握拳";
|
||||
} else if (e.poses.pinch) {
|
||||
this.handStatus = "捏合";
|
||||
// document.getElementById("status").innerText = "捏合";
|
||||
} else {
|
||||
this.handStatus = "张开";
|
||||
// document.getElementById("status").innerText = "张开";
|
||||
}
|
||||
} else if (e.type === EventEnum.LOST) {
|
||||
this.handStatus = "未检测到手";
|
||||
// document.getElementById("status").innerText = "未检测到手";
|
||||
}
|
||||
});
|
||||
},
|
||||
// 获取ios版本号
|
||||
getIOSVersion() {
|
||||
let str = navigator.userAgent.toLowerCase();
|
||||
let ver = str.match(/cpu iphone os (.*?) like mac os/);
|
||||
if (!ver) {
|
||||
console.log("请在Ios系统中打开");
|
||||
} else {
|
||||
console.log(
|
||||
"你当前的Ios系统版本为:" + ver[1].replace(/_/g, ".")
|
||||
);
|
||||
return ver[1].replace(/_/g, ".");
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="less">
|
||||
.hand-track {
|
||||
.prLayout(100%,100vh);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
.video {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
// transform: scaleX(-1);
|
||||
width: 750px;
|
||||
height: auto;
|
||||
// height: 700px;
|
||||
}
|
||||
.info-dialog {
|
||||
.paLayout(0,0, 50%,130px,2);
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
border-radius: 0 0 30px 0;
|
||||
padding: 20px;
|
||||
font-size: 28px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
text-indent: 10px;
|
||||
justify-content: center;
|
||||
div {
|
||||
width: 100%;
|
||||
line-height: 40px;
|
||||
color: #fff;
|
||||
span {
|
||||
font-weight: bold;
|
||||
color: #f00;
|
||||
}
|
||||
}
|
||||
.progress {
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
.paCenterBottom(200px,280px,80px,2);
|
||||
background-color: #fff;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
border-radius: 8px;
|
||||
font-size: 25px;
|
||||
}
|
||||
.unsuit-dialog {
|
||||
visibility: hidden;
|
||||
.paCenterBottom(0, 100%,80px,10);
|
||||
position: fixed;
|
||||
background-color: rgba(255, 255, 255, 0.35);
|
||||
color: #fff;
|
||||
line-height: 80px;
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -7,168 +7,178 @@
|
||||
* @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue
|
||||
-->
|
||||
<template>
|
||||
<div class="home">
|
||||
<Shader></Shader>
|
||||
<div class="overlay"></div>
|
||||
<div class="page grid js_show dialog">
|
||||
<div class="page__hd">
|
||||
<h1 class="page__title" style="font-size: 20px">
|
||||
大板栗的玩具屋
|
||||
</h1>
|
||||
<p class="page__desc" style="font-size: 18px">玩具列表</p>
|
||||
<p class="page__desc">have fun!</p>
|
||||
</div>
|
||||
<div class="weui-grids" style="margin-top: 100px">
|
||||
<div
|
||||
style="opacity: 0.5"
|
||||
@click="jump(1)"
|
||||
class="weui-grid link"
|
||||
>
|
||||
<div class="weui-grid__icon">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p class="weui-grid__label">Color</p>
|
||||
</div>
|
||||
<div
|
||||
style="opacity: 0.5"
|
||||
@click="jump(2)"
|
||||
class="weui-grid link"
|
||||
>
|
||||
<div class="weui-grid__icon">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p class="weui-grid__label">AR Paint</p>
|
||||
</div>
|
||||
<div style="opacity: 0.5" @click="jump(3)" class="weui-grid">
|
||||
<div class="weui-grid__icon">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p class="weui-grid__label">AR HIT</p>
|
||||
</div>
|
||||
<a
|
||||
href="#"
|
||||
class="weui-grid"
|
||||
style="opacity: 0.5"
|
||||
@click="showTips"
|
||||
>
|
||||
<div class="weui-grid__icon">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p class="weui-grid__label">...</p>
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="weui-grid"
|
||||
style="opacity: 0.5"
|
||||
@click="showTips"
|
||||
>
|
||||
<div class="weui-grid__icon">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p class="weui-grid__label">预留</p>
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="weui-grid"
|
||||
style="opacity: 0.5"
|
||||
@click="showTips"
|
||||
>
|
||||
<div class="weui-grid__icon">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p class="weui-grid__label">...</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="home">
|
||||
<Shader></Shader>
|
||||
<div class="overlay"></div>
|
||||
<div class="page grid js_show dialog">
|
||||
<div class="page__hd">
|
||||
<h1 class="page__title" style="font-size: 20px">
|
||||
大板栗的玩具屋
|
||||
</h1>
|
||||
<p class="page__desc" style="font-size: 18px">玩具列表</p>
|
||||
<p class="page__desc">have fun!</p>
|
||||
</div>
|
||||
<div class="weui-grids" style="margin-top: 100px">
|
||||
<div
|
||||
style="opacity: 0.5"
|
||||
@click="jump(1)"
|
||||
class="weui-grid link"
|
||||
>
|
||||
<div class="weui-grid__icon">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p class="weui-grid__label">Color</p>
|
||||
</div>
|
||||
<div
|
||||
style="opacity: 0.5"
|
||||
@click="jump(2)"
|
||||
class="weui-grid link"
|
||||
>
|
||||
<div class="weui-grid__icon">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p class="weui-grid__label">AR Paint</p>
|
||||
</div>
|
||||
<div style="opacity: 0.5" @click="jump(3)" class="weui-grid">
|
||||
<div class="weui-grid__icon">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p class="weui-grid__label">AR HIT</p>
|
||||
</div>
|
||||
<div
|
||||
style="opacity: 0.5"
|
||||
@click="jump(4)"
|
||||
class="weui-grid link"
|
||||
>
|
||||
<div class="weui-grid__icon">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p class="weui-grid__label">摄像机</p>
|
||||
</div>
|
||||
<div class="weui-grid" style="opacity: 0.5" @click="jump(5)">
|
||||
<div class="weui-grid__icon">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p class="weui-grid__label">手势检测</p>
|
||||
</div>
|
||||
<div class="weui-grid" style="opacity: 0.5">
|
||||
<div class="weui-grid__icon">
|
||||
<span class="iconfont"></span>
|
||||
</div>
|
||||
<p class="weui-grid__label">...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import Shader from "@/components/shader/index.vue";
|
||||
|
||||
export default {
|
||||
name: "Index",
|
||||
components: {
|
||||
Shader,
|
||||
},
|
||||
mounted() {
|
||||
// this.$weui.toast('即将跳转至项目列表页', {
|
||||
// duration: 3500,
|
||||
// className: "toast-warn",
|
||||
// });
|
||||
// setTimeout(() => {
|
||||
// location.href = './project-list.html'
|
||||
// }, 3500);
|
||||
},
|
||||
methods: {
|
||||
// 跳转
|
||||
jump(id) {
|
||||
// 第一个游戏
|
||||
if (id == 1) {
|
||||
this.$router.push({
|
||||
name: "Color",
|
||||
params: {},
|
||||
});
|
||||
} else if (id == 2) {
|
||||
this.$router.push({
|
||||
name: "AR",
|
||||
params: {},
|
||||
});
|
||||
} else if (id == 3) {
|
||||
this.$router.push({
|
||||
name: "ARHit",
|
||||
params: {},
|
||||
});
|
||||
}
|
||||
},
|
||||
showTips() {
|
||||
this.$weui.topTips("正在努力开发中……敬请期待", {
|
||||
duration: 1500,
|
||||
className: "custom-classname",
|
||||
callback: function () {
|
||||
// console.log('close');
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
name: "Index",
|
||||
components: {
|
||||
Shader,
|
||||
},
|
||||
mounted() {
|
||||
// this.$weui.toast('即将跳转至项目列表页', {
|
||||
// duration: 3500,
|
||||
// className: "toast-warn",
|
||||
// });
|
||||
// setTimeout(() => {
|
||||
// location.href = './project-list.html'
|
||||
// }, 3500);
|
||||
},
|
||||
methods: {
|
||||
// 跳转
|
||||
jump(id) {
|
||||
// 第一个游戏
|
||||
if (id == 1) {
|
||||
this.$router.push({
|
||||
name: "Color",
|
||||
params: {},
|
||||
});
|
||||
} else if (id == 2) {
|
||||
this.$router.push({
|
||||
name: "AR",
|
||||
params: {},
|
||||
});
|
||||
} else if (id == 3) {
|
||||
this.$router.push({
|
||||
name: "ARHit",
|
||||
params: {},
|
||||
});
|
||||
} else if (id == 4) {
|
||||
this.$router.push({
|
||||
name: "Camera",
|
||||
params: {},
|
||||
});
|
||||
} else if (id == 5) {
|
||||
this.$router.push({
|
||||
name: "HandTrack",
|
||||
});
|
||||
}
|
||||
},
|
||||
showTips() {},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="less">
|
||||
.home {
|
||||
min-height: 100vh;
|
||||
.overlay {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
.paLayout(0,0,100%,100%,101);
|
||||
pointer-events: none;
|
||||
}
|
||||
.link {
|
||||
cursor: pointer;
|
||||
}
|
||||
.dialog {
|
||||
.paLayout(0,0,100%,50%,102);
|
||||
color: #fff;
|
||||
.weui-grids::before,
|
||||
.weui-grids::after,
|
||||
.weui-grid:before,
|
||||
.weui-grid:after {
|
||||
display: none;
|
||||
}
|
||||
.weui-grid__label {
|
||||
color: #fff;
|
||||
}
|
||||
.weui-grid__icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.iconfont {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
.page__hd {
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
min-height: 100vh;
|
||||
.overlay {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
.paLayout(0,0,100%,100%,101);
|
||||
pointer-events: none;
|
||||
}
|
||||
.link {
|
||||
cursor: pointer;
|
||||
}
|
||||
.dialog {
|
||||
.paLayout(0,0,100%,50%,102);
|
||||
color: #fff;
|
||||
.weui-grids {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 30px;
|
||||
.weui-grid {
|
||||
display: inline-block;
|
||||
width: 33.33%;
|
||||
height: 150px;
|
||||
margin-top: 10px;
|
||||
.iconfont {
|
||||
font-size: 40px;
|
||||
}
|
||||
.weui-grid__label {
|
||||
line-height: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
.weui-grids::before,
|
||||
.weui-grids::after,
|
||||
.weui-grid:before,
|
||||
.weui-grid:after {
|
||||
display: none;
|
||||
}
|
||||
.weui-grid__label {
|
||||
color: #fff;
|
||||
}
|
||||
.weui-grid__icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.iconfont {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
.page__hd {
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -20,9 +20,9 @@ import {
|
||||
} from 'gsap'
|
||||
import '@/assets/css/reset.less'
|
||||
// import imgList from '@/preload/imgList.js' // 预加载资源图
|
||||
import weui from 'weui'
|
||||
import '@/assets/css/weui-2.0.1.css';
|
||||
import '@/assets/css/weui-for-work.css'
|
||||
// import weui from 'weui'
|
||||
// import '@/assets/css/weui-2.0.1.css';
|
||||
// import '@/assets/css/weui-for-work.css'
|
||||
// import VueLazyload from 'vue-lazyload' lazyload 懒加载
|
||||
|
||||
// fix swiper6 with vue-awesome-swiper bug
|
||||
@ -40,7 +40,7 @@ Vue.config.productionTip = false
|
||||
// 挂载工具集
|
||||
Vue.prototype.$Utils = utils;
|
||||
// 引入 weui
|
||||
Vue.prototype.$weui = weui
|
||||
// Vue.prototype.$weui = weui
|
||||
|
||||
// init xgl plugins
|
||||
utils.initial({
|
||||
|
||||
@ -12,6 +12,9 @@ import Home from '../Home/index.vue'
|
||||
import Color from '../Color/index.vue'
|
||||
import AR from '../AR/index.vue'
|
||||
import ARHit from '../ARHit/index.vue'
|
||||
import Camera from '../Camera/index.vue'
|
||||
import HandTrack from '../HandTrack/index.vue'
|
||||
|
||||
|
||||
|
||||
Vue.use(VueRouter)
|
||||
@ -62,6 +65,26 @@ const routes = [
|
||||
keepAlive: false, // 是否保持活跃
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/camera",
|
||||
name: "Camera",
|
||||
component: Camera,
|
||||
meta: {
|
||||
title: "camera test", // 标题
|
||||
keepAlive: false, // 是否保持活跃
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/track",
|
||||
name: "HandTrack",
|
||||
component: HandTrack,
|
||||
meta: {
|
||||
title: "Hand Track", // 标题
|
||||
keepAlive: false, // 是否保持活跃
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
// {
|
||||
// path: "/example",
|
||||
// name: "example",
|
||||
|
||||
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@ -11,6 +11,7 @@ const path = require('path');
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
const vConsolePlugin = require("vconsole-webpack-plugin");
|
||||
const TinyimgPlugin = require("tinyimg-webpack-plugin");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, dir);
|
||||
@ -83,7 +84,54 @@ module.exports = {
|
||||
logged: true
|
||||
})]
|
||||
|
||||
// 配置 copy plguins
|
||||
let copyImg = [
|
||||
new CopyWebpackPlugin([
|
||||
// This is required so that yoha can load model files etc.
|
||||
{ from: 'node_modules/@handtracking.io/yoha/', to: 'yoha/' },
|
||||
// Required for github pages...
|
||||
{ from: 'node_modules/coi-serviceworker/coi-serviceworker.min.js', to: './' },
|
||||
])
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
config.plugins = [...config.plugins,
|
||||
...pluginsDev,
|
||||
...tinyimg,
|
||||
...copyImg,
|
||||
];
|
||||
|
||||
config.module.rules.push(
|
||||
// ts加载
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
appendTsSuffixTo: [/\.vue$/],
|
||||
happyPackMode: true // 这个改为true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
//wasm加载
|
||||
{
|
||||
test: /\.wasm$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader', // 解决wasm加载问题
|
||||
options: {
|
||||
name: './static/wasm/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
//模型加载
|
||||
{
|
||||
test: /\.(stl|obj|fbx|mtl|glb|gltf)?$/,
|
||||
@ -102,11 +150,6 @@ module.exports = {
|
||||
}]
|
||||
})
|
||||
|
||||
config.plugins = [...config.plugins,
|
||||
...pluginsDev,
|
||||
...tinyimg
|
||||
];
|
||||
|
||||
// 配置gzip压缩
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user