favicon摸鱼工具开发0.2版本

5,713 阅读5分钟

前几天没啥事,写了一个favicon看视频的工具,挺多小老弟喜欢, 我就又更新了几个功能,写个完整版教程把,代码放github了 github.com/shengxinjin…

新增功能

  1. 视频进度条,上下左右控制视频音量和前进后退
  2. 摄像头直播,新增了简单的怀旧滤镜🐶
  3. 贪食蛇小游戏
    先看效果把

原理很简单,favicon上可放一个32*32的图片,我们动态修改这个图,就可以实现放视频,看摄像头和小游戏的功能,直接看下完整版代码把

放视频

视频功能见上个文章 juejin.cn/post/684490…

音量和视频控制

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)

怀旧滤镜公式

img

代码呼之欲出,不要问逻辑,问就是抄公式,很多滤镜的公式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
		}
	}
}

移动

有上下左右四个方向可以移动,这里偷个懒 单纯的根据方向和蛇头的位置,判断下一个蛇头在的点,然后根据是否吃掉食物,决定尾巴是不是掐掉一个尖即可,比较简单

  1. 下一个蛇头在的位置,如果超出网格,或者是小蛇自己,游戏结束,统计分数放在localstorage里
  2. 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

image-20200402232718316