前幾天沒啥事,寫了一個favicon看視訊的工具,挺多小老弟喜歡, 我就又更新了幾個功能,寫個完整版教程把,程式碼放github了 github.com/shengxinjin…
新增功能
- 視訊進度條,上下左右控制視訊音量和前進後退
- 攝像頭直播,新增了簡單的懷舊濾鏡?
- 貪食蛇小遊戲
先看效果把
原理很簡單,favicon上可放一個32*32的圖片,我們動態修改這個圖,就可以實現放視訊,看攝像頭和小遊戲的功能,直接看下完整版程式碼把
放視訊
視訊功能見上個文章 juejin.im/post/5e8605…
音量和視訊控制
document監聽鍵盤事件,針對上下左右做不同的處理
const DIRECTION = {
37:'left',
// 上
38:'up',
// 右
39:'right',
// 下
40:'down',
}
複製程式碼
const directions = {
left: ()=> this.video.currentTime-=5,
right: ()=> this.video.currentTime+=5,
up: ()=> this.video.volume+=0.1,
down: ()=> this.video.volume-=0.1,
}
document.onkeydown = (event)=> {
// 左上右下 37 38 39 40
let key = event.keyCode
if(key in DIRECTION){
directions[DIRECTION[key]]()
}
}
複製程式碼
搞定
視訊進度條
根據currentTime和duration 可以得到視訊的播放事件和總時長
再根據這邊文章Animating URLs with Javascript and Emojis的啟發,用emoji做個圖形進度條
formatTime(second){
const m = Math.floor(second/60) + ''
const s = parseInt(second%60) + ''
return m.padStart(2,'0')+":"+s.padStart(2,'0')
}
showProgress(){
const current = this.video.currentTime
const total = this.video.duration
const per = Math.floor((current/total)*4)
console.log((current/total).toFixed(2))
const p = ['?', '?', '?', '?', '?'][per]
document.title = `${p}${this.formatTime(current)}/${this.formatTime(total)}`
}
複製程式碼
效果
攝像頭
用webrtc實現很簡單,好像沒啥鳥用的功能,純屬娛樂 可以監控身後有沒有老闆 後續考慮看看加個人臉識別,識別老闆 或者自己笑得過於開心,都發個提醒
let video = document.createElement('video')
video.width=this.width
video.autoplay="autoplay"
document.body.appendChild(video)
this.video = video
const mediaStream = await navigator.mediaDevices.getUserMedia({video:true}) // webrtc
this.video.srcObject = mediaStream
this.video.addEventListener('timeupdate',()=>{
this.videoToImageByFilter()
},false)
複製程式碼
濾鏡
如果你用這個來直播,雖然小了點,但是也可以考慮加一個濾鏡,原理也很簡單,canvas獲取圖片畫素顏色值,對需要的濾鏡,找到公式設定一下就行,比如灰色濾鏡,計算獲取單位元素的RBG然後取平均值 然後複製給自身得到灰色的影像
const context = this.canvas.getContext('2d')
context.clearRect(0, 0, this.SIDE, this.SIDE)
context.drawImage(this.video, 0, 0, this.SIDE, this.SIDE)
//獲取ImageData的屬性:width,height,data(包含 R G B A 四個值);
var imgdata = context.getImageData(0,0,this.SIDE,this.SIDE)
for(var i=0;i<imgdata.data.length;i += 4){
// 灰色濾鏡
// 計算獲取單位元素的RBG然後取平均值 然後複製給自身得到灰色的影像
var avg = (imgdata.data[i]+ imgdata.data[i+1]+ imgdata.data[i+2])/3
imgdata.data[i] = imgdata.data[i+1] =imgdata.data[i+2] =avg
}
this.canvasFilter.getContext('2d').putImageData(imgdata,0,0);
setFavico(this.canvasFilter)
複製程式碼
懷舊濾鏡公式
程式碼呼之欲出,不要問邏輯,問就是抄公式,很多濾鏡的公式baidu都可以搜到,比如卡通,熔爐,黑白等等
// 懷舊濾鏡
const context = this.canvas.getContext('2d')
context.clearRect(0, 0, this.SIDE, this.SIDE)
context.drawImage(this.video, 0, 0, this.SIDE, this.SIDE)
//獲取ImageData的屬性:width,height,data(包含 R G B A 四個值);
var imgdata = context.getImageData(0,0,this.SIDE,this.SIDE)
for(var i = 0; i < imgdata.height * imgdata.width; i++) {
var r = imgdata.data[i*4],
g = imgdata.data[i*4+1],
b = imgdata.data[i*4+2];
var newR = (0.393 * r + 0.769 * g + 0.189 * b);
var newG = (0.349 * r + 0.686 * g + 0.168 * b);
var newB = (0.272 * r + 0.534 * g + 0.131 * b);
var rgbArr = [newR, newG, newB].map((e) => {
return e < 0 ? 0 : e > 255 ? 255 : e;
});
[imgdata.data[i*4], imgdata.data[i*4+1], imgdata.data[i*4+2]] = rgbArr;
}
this.canvasFilter.getContext('2d').putImageData(imgdata,0,0);
setFavico(this.canvasFilter)
複製程式碼
看到favicon中略顯憂鬱的我,感覺回到了殺馬特的青春歲月,可惜現在髮量不行了
貪食蛇
其實32*32畫素,還是可以實現很多有意思的功能,1px變數後,還有30px可以發揮,基本邊長是2和3畫素的點課件, 我們最多可以有15的邊長可以發揮,比如貪食蛇,俄羅斯方塊,有興趣的可以挑戰坦克大戰,推箱子
話不多說,先寫程式碼,貪食蛇原理就是canvas裡坐遊戲,然後渲染favicon,新建一個類
class Snake {
constructor(){
// 15*15
this.SIDE = 32 // favcion邊長32px
this.LINE_WIDTH = 1 // 1px
this.SIZE = 3 // 一個資料點的畫素值
this.WIDTH =10 // 遊戲空間是10個 (32-2)/3
this.initCanvas()
}
initCanvas(){
this.canvas = document.createElement('canvas')
this.canvas.width = this.canvas.height = this.SIDE
}
initGrid(){
this.grid = []
while(this.grid.length<this.WIDTH){
this.grid.push(new Array(this.WIDTH).fill(0))
}
}
}
複製程式碼
初始化
新建好網格後,我們規定0是初始值,小蛇佔的位置,就是1,食物是2
小蛇初始長度是3,在左側居中,
// 初始化小蛇蛇
initSnake(){
this.snake = []
// 初始值長度是3 處置位置在左側中間
let y = 4
let x = 0
let snakeLength = 3
while(snakeLength>0){
this.snake.push({x:x,y:y})
this.grid[y][x] = '1'
snakeLength--
x++
}
// 小蛇的初始方向是右邊
this.current = this.directions.right
}
複製程式碼
我們console.table一下this.grid
bingo渲染網格
我們需要把網格渲染到canvas裡 先把canvas放在body裡,方便除錯 方法和很粗暴,只是個玩具,就不考慮渲染的優化了,直接遍歷網格,每次都重新渲染即可
initCanvas(){
this.canvas = document.createElement('canvas')
this.canvas.width = this.canvas.height = this.SIDE
document.body.appendChild(this.canvas)
}
drawCanvas(){
// 32*32 四周變數1px,所以中間是30*30, 用15*15的格子,每個格子2px
var context = this.canvas.getContext('2d') //getContext() 方法可返回一個物件
context.clearRect(0,0,this.SIDE,this.SIDE)
context.strokeStyle = 'green'
context.lineWidth = this.LINE_WIDTH
context.fillStyle = "red" // 設定或返回用於填充繪畫的顏色、漸變或模式
context.strokeRect(0, 0, this.SIDE, this.SIDE)
this.grid.forEach((row,y)=>{
row.forEach((g,x)=>{
if(g!==0){
// 食物或者是蛇
context.fillRect(this.LINE_WIDTH+x*this.SIZE,this.LINE_WIDTH+y*this.SIZE,this.SIZE,this.SIZE) // x軸 y軸 寬 和 高 ,繪製“被填充”的矩形
}
})
})
setFavico(this.canvas)
}
複製程式碼
看下效果
小蛇出來了 yeah
放食物
這個沒啥說的,隨機一個座標,然後位置落在小蛇上,就重新隨機,否則就設定成2 畫出來也用紅色 然後直接把canvas截個圖,放在favicon上就可以
// 放吃的
setFood(){
while(true){
const x = Math.floor(Math.random()* this.WIDTH)
const y = Math.floor(Math.random()* this.WIDTH)
if(this.grid[y][x]=='1'){
continue
}else{
//食物是2
this.grid[y][x]='2'
break
}
}
}
複製程式碼
移動
有上下左右四個方向可以移動,這裡偷個懶 單純的根據方向和蛇頭的位置,判斷下一個蛇頭在的點,然後根據是否吃掉食物,決定尾巴是不是掐掉一個尖即可,比較簡單
- 下一個蛇頭在的位置,如果超出網格,或者是小蛇自己,遊戲結束,統計分數放在localstorage裡
- setInterval定時移動就可以,根據方向
this.directions = {
// 左
'left':{x:-1,y:0},
// 上
'up':{x:0,y:-1},
// 右
'right':{x:1,y:0},
// 下
'down':{x:0,y:1},
}
複製程式碼
move(){
// 1. 根據方向,計算出下一個蛇頭所在位置
// 蛇頭設為
const head = this.snake[this.snake.length-1]
const tail = this.snake[0]
const nextX = head.x+this.current.x
const nextY = head.y+this.current.y
// 2. 判斷蛇頭是不是出界 是不是碰見自己個了 如果是屁股尖就碰不到
const isOut = nextX<0||nextX>=this.WIDTH||nextY<0||nextY>=this.WIDTH
if(isOut){
this.initGame()
return
}
const isSelf = (this.grid[nextY][nextX]) =='1' && !(nextX === tail.x && nextY === tail.y)
if(isSelf){
this.initGame()
return
}
const isFood = this.grid[nextY][nextX]=='2' // 食物是2
if(!isFood){
// 所謂蛇向前走,就是把尾巴去掉, 新增nextX和Y
// 去尾巴 有食物的時候不用去尾
this.snake.shift()
this.grid[tail.y][tail.x] = 0
}else{
// 食物吃掉了,在放一個
this.setFood()
// 加一份
this.score++
this.setTitle()
}
// 新增頭部
this.snake.push({x:nextX,y:nextY})
this.grid[nextY][nextX] = '1'
this.drawCanvas()
}
複製程式碼
後續
基本功能也就這些了,自己摸魚夠用了,這樣就可以開3個tab
- 1號tab看小視訊
- 2號tab開攝像頭,防止後面班主任
- 3號tab玩貪食蛇 其實30 * 30畫素可以做的東西還挺多,比如俄羅斯方塊,魔塔,坦克大戰,都是能放下的,有興趣的可以試試
程式碼地址: github.com/shengxinjin… 過兩天把0.2的程式碼也錄個視訊放B站把,0.1版本視訊歡迎三連
建個群把,一起摸魚,進不去的加我微信 woniu_ppp