window.cancelAnimationFrame =
window.cancelAnimationFrame || window.mozCancelAnimationFrame;
window.requestAnimationFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.mzRequestAnimationFrame ||
window.oRequestAnimationFrame;
// this.requestAnimationFrame = requestAnimationFrame(this.recursion_draw_circle)
// cancelAnimationFrame(this.requestAnimationFrame)
class cdr {
constructor(option) {
this.canvas = option.canvas;
// 线的粗细
this.lineHeight = 1.5;
// 圆距离上下的间隙
this.lineGap = 2;
this.radius = 20;
let bw = this.radius * 2 + this.lineHeight + this.lineGap * 2
// x轴格数
// this.XRowNumber = Math.floor(window.screen.width / bw);
this.XRowNumber = 11;
this.canvas.width = this.XRowNumber * bw - this.lineHeight;
this.canvas.height = this.XRowNumber * bw - this.lineHeight;
this.cxt = this.canvas.getContext('2d');
this.moveSpeed = 1; // 移动速度
this.mounthSpeed = 0.05; // 嘴的速度
this.ballColor = ["#c36f6f", "#d69347", "#81b91f", "#0fab11", "#0aa99e", "#0a4da9", "#de26d4"];
// 球的位置
this.ballDirection = ["top", "left", "right", "bottom"];
// 球张嘴闭嘴 控制数据
this.ballAngle = {
top: {
maxSAngle: 3 / 2 * Math.PI - 1 / 4 * Math.PI, // 起始角度
maxEAngle: 1 / 4 * Math.PI + 3 / 2 * Math.PI, // 结束角度
avgAngle: 3 / 2 * Math.PI,
counterclockwise: true // 旋转方向
},
left: {
maxSAngle: 3 / 4 * Math.PI, // 起始角度
maxEAngle: 3 / 2 * Math.PI - 1 / 4 * Math.PI, // 结束角度
avgAngle: Math.PI,
counterclockwise: true // 旋转方向
},
right: {
maxSAngle: 1 / 4 * Math.PI, // 起始角度
maxEAngle: 3 / 2 * Math.PI + 1 / 4 * Math.PI, // 结束角度
avgAngle: 0,
counterclockwise: false // 旋转方向
},
bottom: {
maxSAngle: 1 / 2 * Math.PI + 1 / 4 * Math.PI, // 起始角度
maxEAngle: 1 / 2 * Math.PI - 1 / 4 * Math.PI, // 结束角度
avgAngle: 1 / 2 * Math.PI,
counterclockwise: false // 旋转方向
}
}
this.PI2 = 2 * Math.PI
this.ballArray = [];
this.lineY = [];
// 障碍物墙的坐标
this.wallArray = [];
// 绘制障碍物的数据 Obstacle
this.obstacle = [];
// 记录可以十字路口的坐标 数据
this.crossingArray = []
this.run();
}
run() {
this.drawColLine();
this.initBallArray();
this.initWallArray();
this.initObstacleArray();
this.drawBall()
}
setCounterclockwise(type) {
// "top", "left", "right", "bottom"
if (type == 'top' || type == 'left')
return true
else
return false
}
initWallArray() {
// direction 方向 top left right bottom
// 设置四周墙的坐标
// 上
this.wallArray.push({
direction: "top",
coordinate: {
startX: 0,
startY: 0,
endX: this.canvas.width,
endY: 0
}
})
// 下
this.wallArray.push({
direction: "bottom",
coordinate: {
startX: 0,
startY: this.canvas.height,
endX: this.canvas.width,
endY: this.canvas.height
}
})
// 左
this.wallArray.push({
direction: "left",
coordinate: {
startX: 0,
startY: 0,
endX: 0,
endY: this.canvas.height
}
})
// 右
this.wallArray.push({
direction: "right",
coordinate: {
startX: this.canvas.width,
startY: 0,
endX: this.canvas.width,
endY: this.canvas.height
}
})
}
initBallArray() {
this.ballArray = new Array(this.random(22, 55))
// this.ballArray = [4]
for (let i = 0; i < this.ballArray.length; i++) {
this.ballArray[i] = {
// x: this.random(this.radius + 5, this.canvas.width - this.radius - 5),
// y: this.setBallY(),
x:this.radius + this.lineGap,
y:this.radius + this.lineGap,
direction: this.getDirection(),
fillStyle: this.ballColor[this.random(0, 7)],
directionType: 1, // 1 闭嘴 2 张嘴
moveSpeed : this.random2(0.8,1.5)
}
}
}
initObstacleArray(){
// 绘制障碍物
let rctW = (this.canvas.width - (this.radius*2 + this.lineGap * 2) * 4) / 3
let space = this.radius*2 + this.lineGap * 2
let rctH = (this.canvas.width - (this.radius*2 + this.lineGap * 2) * 7) / 6
for (let i = 0; i < 6; i++) {
for(let j = 0 ; j < 3 ; j++){
this.obstacle.push({
x: space * (j+1) + rctW*j ,
y : space * (i + 1) + rctH * i,
width: rctW,
height:rctH,
radius:6,
color:"green",
type: "fill"
})
this.crossingArray.push({
x: Math.round( space * (j+1) + rctW*j - this.radius - this.lineGap ),
y :Math.round( space * (i + 1) + rctH * i - this.radius - this.lineGap)
})
if( j == 2) {
this.crossingArray.push({
x: Math.round( space * (j+2) + rctW*(j + 1) - this.radius - this.lineGap),
y : Math.round( space * (i + 1) + rctH * i - this.radius - this.lineGap)
})
}
// this.wallArray.push({
// direction: "top",
// coordinate: {
// startX: 0,
// startY: 0,
// endX: this.canvas.width,
// endY: 0
// }
// })
}
}
}
getDirection() {
let direction = this.ballDirection[Math.floor(this.random(0, 4))]
return {
d: direction,
sAngle: this.ballAngle[direction].maxSAngle,
eAngle: this.ballAngle[direction].maxEAngle,
counterclockwise: this.ballAngle[direction].counterclockwise,
}
}
setBallY() {
let y = this.random(this.radius, this.canvas.height);
let min = 99999, newY = 0
this.lineY.map(v => {
if (Math.abs(y - v) < min) {
min = Math.abs(y - v)
newY = v
}
})
return newY - this.radius - this.lineGap - this.lineHeight / 2
}
drawColLine() {
this.lineY = []
let OldHeight = 0
for (let i = 1; i <= this.XRowNumber; i++) {
if (i == 1) {
OldHeight += this.radius * 2 + this.lineGap * 2
} else {
OldHeight += this.radius * 2 + this.lineGap * 2 + this.lineHeight
}
this.lineY.push(OldHeight)
}
this.lineY.push(this.radius * 2 + OldHeight + this.lineGap * 2 + this.lineHeight)
}
/**
* @description {画直线}
* @param {*开始位置对象} start
* @param {*结束位置对象} end
* @param {*线的宽度} width
* @param {*线的颜色} strokeStyle
*/
drawLine(start, end, width = 2, strokeStyle = "#000000") {
this.cxt.beginPath();
this.cxt.strokeStyle = strokeStyle;
this.cxt.lineWidth = this.lineHeight; //线宽
this.cxt.moveTo(start.x, start.y); // 线开始位置
this.cxt.lineTo(end.x, end.y); // 线 结束位置
this.cxt.stroke();
}
drawBall() {
this.clearCanvas();
this.drawColLine();
this.ballArray.map(item => {
this.cxt.fillStyle = item.fillStyle;
this.cxt.beginPath();
this.cxt.arc(item.x, item.y, this.radius, item.direction.sAngle, item.direction.eAngle, item.direction.counterclockwise);
// 更改球的张嘴闭嘴数据
this.updateBallDirection(item)
// 更改球的坐标数据
this.updateBall_X_OR_Y(item)
this.cxt.lineTo(item.x, item.y);
this.cxt.closePath();
this.cxt.fill();
})
// 绘制障碍物
this.obstacle.map(item =>{
this.drawRectangle.apply(this,Object.values(item))
})
requestAnimationFrame(this.drawBall.bind(this))
}
drawRectangle(x, y, width, height, radius, color, type) {
let ctx = this.cxt
ctx.beginPath();
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y + height - radius);
ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
ctx.lineTo(x + width - radius, y + height);
ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
ctx.lineTo(x + width, y + radius);
ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
ctx.lineTo(x + radius, y);
ctx.quadraticCurveTo(x, y, x, y + radius);
ctx[type + 'Style'] = color || params.color;
ctx.closePath();
ctx[type]();
}
setBallDirection(item) {
item.direction.d = this.ballDirection[Math.floor(this.random(0, 4))]
item.direction.counterclockwise = this.setCounterclockwise(item.direction.d) // 旋转方向
item.direction.sAngle = this.ballAngle[item.direction.d].maxSAngle
item.direction.eAngle = this.ballAngle[item.direction.d].maxEAngle
}
/**
* description {更改Ball数组中某个节点的张嘴闭嘴 数据}
* @param {*} item
*/
updateBall_X_OR_Y(item) {
// 找到前方最近的障碍物 位置
this.wallArray.map(child => {
if (child.direction == item.direction.d) {
if (child.direction == "top") {
// 向上行走
// 先确定前面没有障碍物
// 1 确定x轴某个范围
if ((item.x - this.radius) >= child.coordinate.startX && (item.x + this.radius) <= child.coordinate.endX) {
// 2. 过滤反方向的障碍物
if ((item.y - this.radius - this.lineGap) > child.coordinate.startY) {
// 开始移动
item.y -= item.moveSpeed
if ((item.y - this.radius - this.lineGap) <= child.coordinate.startY) {
item.y = child.coordinate.startY + this.radius + this.lineGap
}
} else {
this.setBallDirection(item)
}
}
}
if (child.direction == "bottom") {
// 向下行走
// 先确定前面没有障碍物
// 1 确定x轴某个范围
if ((item.x - this.radius) >= child.coordinate.startX && (item.x + this.radius) <= child.coordinate.endX) {
// 2. 过滤反方向的障碍物
if ((item.y + this.radius + this.lineGap) < child.coordinate.endY) {
// 开始移动
item.y += item.moveSpeed
if ((item.y - this.radius - this.lineGap) >= child.coordinate.endY) {
item.y = child.coordinate.startY - this.radius - this.lineGap
}
} else {
this.setBallDirection(item)
}
}
}
if (child.direction == "left") {
// 向左行走
// 先确定前面没有障碍物
// 1 确定x轴某个范围
if ((item.y - this.radius) >= child.coordinate.startY && (item.y + this.radius) <= child.coordinate.endY) {
// 2. 过滤反方向的障碍物
if ((item.x - this.radius - this.lineGap) > child.coordinate.startX) {
// 开始移动
item.x -= item.moveSpeed
if ((item.x - this.radius - this.lineGap) <= child.coordinate.startX) {
item.x = child.coordinate.startX + this.radius + this.lineGap
}
} else {
this.setBallDirection(item)
}
}
}
if (child.direction == "right") {
// 向左行走
// 先确定前面没有障碍物
// 1 确定x轴某个范围
if ((item.y - this.radius) >= child.coordinate.startY && (item.y + this.radius) <= child.coordinate.endY) {
// 2. 过滤反方向的障碍物
if ((item.x + this.radius + this.lineGap) < child.coordinate.endX) {
// 开始移动
item.x += item.moveSpeed
if ((item.x + this.radius + this.lineGap) >= child.coordinate.endX) {
item.x = child.coordinate.endX - this.radius - this.lineGap
}
} else {
this.setBallDirection(item)
}
}
}
}
})
// console.log(Math.floor(item.x),Math.floor(item.y))
this.crossingArray.map(cd=>{
if(Math.abs(item.x - cd.x) < 0.7 && Math.abs(item.y - cd.y) < 0.7 ){
item.x = cd.x
item.y = cd.y
this.setBallDirection(item)
}
})
}
/**
* description {更改Ball数组中某个节点的张嘴闭嘴 数据}
* @param {指定节点内容} item
*/
updateBallDirection(item) {
let avgAngle = this.ballAngle[item.direction.d].avgAngle
let maxSAngle = this.ballAngle[item.direction.d].maxSAngle
let maxEAngle = this.ballAngle[item.direction.d].maxEAngle
// 张嘴,闭嘴动画 , // 1 闭嘴 2 张嘴
if (item.direction.d == 'bottom') {
// this.PI2
if (item.directionType == 1) {
item.direction.sAngle -= this.mounthSpeed
if (item.direction.sAngle < avgAngle + this.mounthSpeed) {
item.direction.sAngle = avgAngle + this.mounthSpeed
item.directionType = 2
}
item.direction.eAngle += this.mounthSpeed
if (item.direction.eAngle > avgAngle - this.mounthSpeed) {
item.direction.eAngle = avgAngle - this.mounthSpeed
item.directionType = 2
}
}
if (item.directionType == 2) {
item.direction.sAngle += this.mounthSpeed
if (item.direction.sAngle > maxSAngle) {
item.direction.sAngle = maxSAngle - this.mounthSpeed
item.directionType = 1
}
item.direction.eAngle -= this.mounthSpeed
if (item.direction.eAngle < maxEAngle) {
item.direction.eAngle = maxEAngle + this.mounthSpeed
item.directionType = 1
}
}
}
if (item.direction.d == 'left' || item.direction.d == 'top') {
// this.PI2
if (item.directionType == 1) {
item.direction.sAngle += this.mounthSpeed
if (item.direction.sAngle > avgAngle - this.mounthSpeed) {
item.direction.sAngle = avgAngle - this.mounthSpeed
item.directionType = 2
}
item.direction.eAngle -= this.mounthSpeed
if (item.direction.eAngle < avgAngle + this.mounthSpeed) {
item.direction.eAngle = avgAngle + this.mounthSpeed
item.directionType = 2
}
}
if (item.directionType == 2) {
item.direction.sAngle -= this.mounthSpeed
if (item.direction.sAngle < maxSAngle) {
item.direction.sAngle = maxSAngle + this.mounthSpeed
item.directionType = 1
}
item.direction.eAngle += this.mounthSpeed
if (item.direction.eAngle > maxEAngle) {
item.direction.eAngle = maxEAngle - this.mounthSpeed
item.directionType = 1
}
}
}
if (item.direction.d == 'right') {
// maxSAngle = this.PI2
if (item.directionType == 1) {
// avgAngle = this.PI2
avgAngle = 0
item.direction.sAngle -= this.mounthSpeed
if (item.direction.sAngle < avgAngle + 0.001) {
item.direction.sAngle = avgAngle + 0.001
item.directionType = 2
}
item.direction.eAngle += this.mounthSpeed
avgAngle = this.PI2
if (item.direction.eAngle > avgAngle - 0.001) {
item.direction.eAngle = avgAngle - 0.001
item.directionType = 2
}
}
if (item.directionType == 2) {
item.direction.sAngle += this.mounthSpeed
if (item.direction.sAngle > maxSAngle) {
item.direction.sAngle = maxSAngle - + 0.001
item.directionType = 1
}
item.direction.eAngle -= this.mounthSpeed
if (item.direction.eAngle < maxEAngle) {
item.direction.eAngle = maxEAngle + + 0.001
item.directionType = 1
}
}
}
}
clearCanvas() {
this.cxt.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
/**
* @description {生成随机整数}
* @param {下限} lower
* @param {上限} upper
*/
random(lower, upper) {
return Math.floor(Math.random() * (upper - lower)) + lower;
}
random2(lower, upper) {
return Math.random() * (upper - lower) + lower;
}
}
new cdr({
canvas: document.getElementById("chidoudou")
})
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>吃豆人</title>
<style>
html,body{
margin: 0;
padding: 0;
}
canvas{
display: block;
background: #ffd;
}
</style>
</head>
<body>
<div class="example-box">
<canvas id="chidoudou"></canvas>
</div>
</body>
<script src="../js/cdr.js"></script>
</html>
gulpfile.js
var gulp = require('gulp');
// 在命令行设置为生产环境或者开发环境
//开发环境不要使用压缩,会影响找错
// windows: set NODE_ENV = development 或 production
//(可能会有问题, 建议使用 :var environment = process.env.NODE_ENV || 'development';)
// mac linux : export NODE_ENV = development 或 production
var environment = process.env.NODE_ENV || 'development';
//根据自己开发的实际需求自行设置, src放开发文件, dist是打包压缩后的导出目录
const folder = {
src: "canvas/",
dist: 'dist/'
}
// 压缩html代码
const htmlClean = require('gulp-htmlclean');
// 图片类: 压缩PNG, JPEG, GIF and SVG
const imageMin = require('gulp-imagemin');
// uglify 不支持压缩 es6 , 需要先使用babel降级才行 */
const uglifyJS = require('gulp-uglify')
//es6 降级到es5 请使用 "gulp-babel": "^7.0.1",
//切记不要用 8版本, 会出现无法输出的情况
const babel = require('gulp-babel');
//去除掉 注释, console 和 debugger
const removeComments = require('gulp-strip-debug')
//less 转 css
const less = require('gulp-less');
//css3 兼容各类浏览器脚本
const postCss = require('gulp-postcss');
const autoPrefixer = require('autoprefixer');
//css代码压缩
const cleanCss = require('gulp-clean-css');
//创建服务器环境插件 支持热更新
const connect = require("gulp-connect");
gulp.task('html', function () {
const step = gulp.src(folder.src + "html/*")
.pipe(connect.reload())
if (environment == 'production') {
step.pipe(htmlClean())
}
step.pipe(gulp.dest(folder.dist + "html/"))
})
gulp.task('img', function () {
gulp.src(folder.src + "img/*")
.pipe(imageMin())
.pipe(gulp.dest(folder.dist + "img/"))
})
gulp.task('css', function () {
var step = gulp.src(folder.src + "css/*")
.pipe(connect.reload())
.pipe(less())
.pipe(postCss([autoPrefixer()]))
if (environment == 'production') {
step.pipe(cleanCss())
}
step.pipe(gulp.dest(folder.dist + "css/"))
})
gulp.task('js', function () {
var step = gulp.src(folder.src + "js/*")
.pipe(connect.reload())
.pipe(babel({
presets: ['es2015']
}))
if (environment == 'production') {
step.pipe(removeComments())
.pipe(uglifyJS())
}
step.pipe(gulp.dest(folder.dist + "js/"))
})
gulp.task('server', function () {
//设置默认服务器接口, livereload: true 是否监视文件变化
connect.server({
port: 8888,
livereload: true
})
})
//自动刷新页面
gulp.task('watch', () => {
gulp.watch(folder.src + "html/*", ['html']);
gulp.watch(folder.src + "css/*", ['css']);
gulp.watch(folder.src + "js/*", ['js'])
})
gulp.task("default", ["html", "img", "css", "js", "server", "watch"]);
// default任务一定要写,不然会报警告: Task 'default' is not in your gulpfile
// 数组中写哪一个执行哪一个任务, 从左到右执行
package.json
{
"name": "gulp_es6",
"version": "1.0.0",
"description": "",
"main": "gulpfile.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"autoprefixer": "^9.5.1",
"babel-core": "^6.26.3",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.24.1",
"gulp": "^3.9.1",
"gulp-babel": "^7.0.1",
"gulp-clean-css": "^4.2.0",
"gulp-connect": "^5.7.0",
"gulp-connect-reproxy": "^0.0.98",
"gulp-htmlclean": "^2.7.22",
"gulp-imagemin": "^5.0.3",
"gulp-less": "^4.0.1",
"gulp-postcss": "^8.0.0",
"gulp-strip-debug": "^3.0.0",
"gulp-uglify": "^3.0.2"
},
"author": "",
"license": "ISC"
}