写在前面
感谢各位在前端一直努力奋斗的程序猿们,希望以后的HelloWorld会更美好。
感谢来看看机智的前端童鞋怎么防盗作者的分享
感谢停了一天的网终于恢复
干货
先来分享2张图
1)王的愤怒
2) 程序猿的愤怒
原理
通过启用浏览器摄像的方式,把每一帧的图映射到canvas上,通过比较上一帧与当前帧的差异,算出来的差异占的百分比,超过某个百分比就触发函数。
栗子:我摇动我的双手,两个帧的手的位置不一样,从而差异占的比例就挺大的,就触发了写好的回调函数{
给水杯添加一个动画
给body更换一个背景颜色
}
程序猿的愤怒是通过在来看看机智的前端童鞋怎么防盗借鉴的代码实现的,
原作者项目GitHub地址
同时,在不破坏原作者源码的基础下,愤怒的程序猿代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>愤怒的程序猿</title>
<style type="text/css">
.room{
text-align: center;
margin: 200px 10px;
position: relative;
height: 240px;
width: 320px;
display: inline-block;
}
.room>*{
float: left;
position: absolute;
bottom: 0;
left: 100px;
}
.room .beizi{
width: 50px;
height: 100px;
border-radius: 10px;
background-color: darkolivegreen;
color: white;
line-height: 100px;
}
.room>span{
width: 100%;
left: 80px; color: white;
}
canvas{
/*display: none;*/
}
@keyframes tiao{0%{transform: translateY(0);}50%{transform: translateY(-100%);}100%{transform: translateY(0);}}
@-webkit-keyframes tiao{0%{transform: translateY(0);}50%{transform: translateY(-100%);}100%{transform: translateY(0);}}
.tiao{
animation: tiao 1s ease-in-out .1s;
-webkit-animation: tiao 1s ease-in-out .1s;
}
</style>
</head>
<body>
<div class="room">
<video autoplay width="320" height="240"></video>
<div class="beizi">水杯</div>
<span>您可以尝试敲打桌面</span>
</div>
<div class="room" style="display: none;">
<canvas width="320" height="240"></canvas>
<span>每帧截取</span>
</div>
<div class="room">
<canvas width="320" height="240"></canvas>
<span>上一帧与这一帧的对比差异</span>
</div>
<div class="room">
<canvas width="320" height="240"></canvas>
<span>检测到动了一定幅度的截图</span>
</div>
<script>
var w = 320,
h = 240;
var video = document.querySelector('video'),
beizi = document.querySelector(".beizi"),
canvas = document.querySelectorAll('canvas'),
canvasForDiff = canvas[1];
canvasPhoto = canvas[2];
canvas = canvas[0];
navigator.getUserMedia || navigator.webkitGetUserMedia// || navigator.mozGetUserMedia//ie chrome firefox
({video:true}, function(stream) {
video.src = window.URL.createObjectURL(stream);
video.play();
}, function(err) {
alert('出错: ' + err)
});
//canvas
var context = canvas.getContext('2d'),
diffCtx = canvasForDiff.getContext('2d'),
photo = canvasPhoto.getContext('2d');
//将第二个画布混合模式设为“差异”
diffCtx.globalCompositeOperation = 'difference';
var preFrame, //前一帧
curFrame; //当前帧
var diffFrame; //存放差异帧的imageData
//捕获并保存帧内容
function captureAndSaveFrame(){
preFrame = curFrame;
context.drawImage(video, 0, 0, w, h);
curFrame = canvas.toDataURL(); //转为base64并保存
}
//绘制base64图像到画布上
function drawImg(src, ctx){
ctx = ctx || diffCtx;
var img = new Image();
img.src = src;
ctx.drawImage(img, 0, 0, w, h);
}
//渲染前后两帧差异
function renderDiff(){
if(!preFrame || !curFrame) return;
diffCtx.clearRect(0, 0, w, h);
drawImg(preFrame);
drawImg(curFrame);
diffFrame = diffCtx.getImageData( 0, 0, w, h ); //捕获差异帧的imageData对象
}
//计算差异
function calcDiff(){
if(!diffFrame) return 0;
var cache = arguments.callee,
count = 0;
cache.total = cache.total || 0; //整个画布都是白色时所有像素的值的总和
for (var i = 0, l = diffFrame.width * diffFrame.height * 4; i < l; i += 4) {
count += diffFrame.data[i] + diffFrame.data[i + 1] + diffFrame.data[i + 2];
if(!cache.isLoopEver){ //只需在第一次循环里执行
cache.total += 255 * 3; //单个白色像素值
}
}
cache.isLoopEver = true;
count *= 3; //亮度放大
//返回“差异画布高亮部分像素总值”占“画布全亮情况像素总值”的比例
return Number(count/cache.total).toFixed(2);
}
var t,d;
//定时捕获
function timer(delta){
d = 0;
setTimeout(function(){
captureAndSaveFrame();
renderDiff();
setTimeout(function(){
if((d=calcDiff())>=0.13){
handel();
}
}, 16.7);
timer(delta)
}, delta || 500);
}
function handel(){
if(t){return}
photo.drawImage(video, 0, 0, w, h);
beizi.classList.add('tiao');
canvasForDiff.classList.add('tiao');
document.body.style.backgroundColor = (function(){
return ['red','yellow','black','blue','green','white'][Math.floor(Math.random()*6)];
}());
t = setTimeout(function(){
canvasForDiff.classList.remove('tiao');
beizi.classList.remove('tiao');
t = null;
},1000);
}
timer();
</script>
</body>
</html>
- Q:在谷歌浏览器上运行的,表示页面一片空白,啥都没有,请问这是为什么?
- A:通过谷歌的文档可以得知,这是为了安全性考虑,非 HTTPS 的服务端请求都不能接入摄像头,简单来说,chrome通过文件的方式打开一个html文件,是无法开启摄像头的,可以在本地服务端打开哦!如:localhost
- 还要注意写兼容哦!楼主只写了chrome的
嗨起来
程序猿们怎么可能就地止步,让我们运用代码,自己来写一个自动拍照吧!
- AV.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<style>
div{
text-align: center;
width: 250px;
margin: 10px 0;
}
</style>
<body>
<video></video>
<div>
<button onclick="paipaipai()">手动拍照</button>
<button onclick="auto()">自动拍照</button>
<button onclick="debug()">调试模式</button>
</div>
<img/>
<script src="av.js"></script>
<script>
var img = document.querySelector("img");
var av = Av({
//style: {
// width: 320,//视频 画布 宽
// height: 240//高
//},
//deg: 0.12,//灵敏度 触发动作幅度
//die: 500,//dwon机时间,触发事件后500ms内不触发
//delta:300,//取帧间隔 300ms 获取一次视频帧
//sw:true//开关,默认为开
fn:function(data){//触发函数
img.src = data;
}
});
function paipaipai(){
av.switchAV(false);
img.src = av.getCurFrame();
}
function auto(){
av.switchAV(true);
}
function debug(){
[].map.call(document.querySelectorAll("canvas"),function(c){
c.style.display = 'inline';
});
}
</script>
</body>
</html>
- AV.js
(function() {
var av = function(option) {
option = option || {};
this.op = (function(o) {
for(var i in option) {
o[i] = option[i];
}
return o;
}({
style: {
width: 320,//视频 画布 宽
height: 240//高
},
deg: 0.12,//灵敏度 触发动作幅度
die: 500,//dwon机时间,触发事件后500ms内不触发
delta:300,//取帧间隔 300ms 获取一次视频帧
sw:true//开关,默认为开
}));
this.initEm();
this.initMedia();
this.initCanvas();
this.switchAV();
}
var avpro = av.prototype;
//初始化基础元素
avpro.initEm = function() {
//video元素
this.video = document.querySelector(this.op.el || 'video');
this.video.setAttribute('autoplay', '');
this.video.style.objectFit = 'fill';
//初始化canvas元素
var canvas = document.createElement("canvas");
canvas.style.display = 'none';
canvas.style.backgroundColor = this.video.style.backgroundColor = 'grey';
canvas.style.width = this.video.style.width = (this.w = canvas.width = this.op.style.width) + 'px';
canvas.style.height = this.video.style.height = (this.h = canvas.height = this.op.style.height) + 'px';
//动作画布克隆,映射视频 ac 动作Action 便于记忆
var acCanvas = canvas.cloneNode(true);
//对比画布克隆,比较差异 bw 表示黑白 便于记忆
var bwCanvas = canvas.cloneNode(true);
//清除原体释放资源
canvas = null;
//添加至页面
document.body.appendChild(acCanvas);
document.body.appendChild(bwCanvas);
this.canvas = acCanvas;
this.acCanvas = acCanvas.getContext('2d');
this.bwCanvas = bwCanvas.getContext('2d');
}
//初始化摄像头
avpro.initMedia = function() {
var tv = this.video;
navigator.getUserMedia || navigator.webkitGetUserMedia //ie chrome
({
video: true
}, function(se) {
tv.src = window.URL.createObjectURL(se);
tv.play();
}, function(err) {
console.log('err:' + err);
});
}
//初始化画布,帧数
avpro.initCanvas = function() {
//将第二个画布混合模式设为“差异”
this.bwCanvas.globalCompositeOperation = 'difference';
// 前一帧 当前帧 差异帧
this.preFrame = this.curFrame = this.diffFrame = null;
}
//开始AV
avpro.startAv = function() {
var tv = this;
var call = arguments.callee;
tv.zt = setTimeout(function() {
tv.avSaveFrame();
tv.renderDiff();
setTimeout(function() {
if(tv.calcDiff() >= tv.op.deg) {
//触发事件
tv.handel();
}
}, 16.7);
call.call(tv);
},tv.op.delta);
}
//设置开关 和 回调函数
avpro.switchAV = function(sw,fn){
if(sw == undefined ? this.op.sw:sw){
this.startAv();
}else{
this.stopAv();
}
fn && (this.op.fn = fn);
}
avpro.stopAv = function(){
this.zt && clearTimeout(this.zt);
}
//触发事件
avpro.handel = function() {
var tv = this;
if(tv.t) {
return;
}
console.log(tv.fn);
tv.op.fn && tv.fn(tv.curFrame);
tv.t = setTimeout(function() {
tv.t = null;
},tv.op.die);
}
avpro.getCurFrame = function(){
return this.curFrame;
}
//捕获并保存帧
avpro.avSaveFrame = function() {
//帧替换
this.preFrame = this.curFrame;
this.acCanvas.drawImage(this.video, 0, 0, this.w, this.h);
//转为base64并保存当前帧
this.curFrame = this.canvas.toDataURL();
}
//绘制base64图像到画布上
avpro.drawImg = function(src, ctx) {
ctx = ctx || this.bwCanvas;
var img = new Image();
img.src = src;
ctx.drawImage(img, 0, 0 ,this.w, this.h);
}
//渲染前后两帧差异
avpro.renderDiff = function() {
if(!this.preFrame || !this.curFrame) return;
this.bwCanvas.clearRect(0, 0, this.w, this.h);
this.drawImg(this.preFrame);
this.drawImg(this.curFrame);
//捕获差异帧的imageData对象
this.diffFrame = this.bwCanvas.getImageData(0, 0, this.w, this.h);
}
//计算差异
avpro.calcDiff = function() {
if(!this.diffFrame) return 0;
var cache = arguments.callee,
count = 0;
cache.total = cache.total || 0; //整个画布都是白色时所有像素的值的总和
for(var i = 0, l = this.diffFrame.width * this.diffFrame.height * 4; i < l; i += 4) {
count += this.diffFrame.data[i] + this.diffFrame.data[i + 1] + this.diffFrame.data[i + 2];
if(!cache.isLoopEver) { //只需在第一次循环里执行
cache.total += 255 * 3; //单个白色像素值
}
}
cache.isLoopEver = true;
count *= 3; //亮度放大
//返回“差异画布高亮部分像素总值”占“画布全亮情况像素总值”的比例
return Number(count / cache.total).toFixed(2);
}
var nav = null;
window.Av = function(op) {
return nav || (nav = new av(op));
};
}())
遗言
写前圣成佛:感觉自己马上要写出一篇无与伦比的文章。
写后呆成魔:写的跟流水账样的。
最后希望宝强能够好起来。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。