序言
1. Fabricjs 介绍
//生成fabric的canvas对象const canvas = new fabric.Canvas('c');//创建一个指定左上坐标,指定宽高,填充为蓝色的矩形框const rect = new fabric.Rect({left: 100,top: 100,fill: 'blue',width: 150,height: 100});//创建一个指定左上,半径50的圆,并填充为红色const circle = new fabric.Circle({left: 150,top: 150,radius: 50,fill: 'red'});//把上面两个组合成一个组,并指定左上角const group = new fabric.Group([rect, circle], {left: 50,top: 50});canvas.add(group);
2. 目标:多选9宫格
3. 解决随机问题
//洗牌shuffle = function (arr) {for (let i = arr.length - 1; i >= 0; i--) {let temRandom = Math.floor(Math.random() * i);var tmp = arr[i];arr[i] = arr[temRandom];arr[temRandom] = tmp;}return arr;};
//来个坐标游戏类,我们让它完成随机单元格的输出//原谅我,这里是原生js,并非ts!class coordinateGame {size;positions;constructor(values, size) {this.size = size;this.positions = [];this.values = this.shuffle(values || []);console.log(this.values);for (var i = 0; i < 3; i++) {for (var j = 0; j < 3; j++) {var index = 3 * i + j;var val = index >= this.values.length ? '' : this.values[index];this.positions[index] = {coordinate: [j, i],selected: false,value: val.toString(),index: index,};}}}//洗牌shuffle = function (arr) {for (let i = arr.length - 1; i >= 0; i--) {let temRandom = Math.floor(Math.random() * i);var tmp = arr[i];arr[i] = arr[temRandom];arr[temRandom] = tmp;}return arr;};findNextPos = function () {var unSelectedPos = this.positions.filter((item) => !item.selected);if (unSelectedPos.length == 0) return ;var rtn = unSelectedPos[0];rtn.selected = true;return {coordinate: [...rtn.coordinate],value: rtn.value,index: rtn.index,};};}
4. 画出小格子
draw() {//这里的canvas是fabricjs的canvas对象var canvas = this.canvas;//使用坐标游戏类获取位置var rndPos = this.//如果需要给定选中值,那么我们浅浅的克隆下const tmpAnswer = [...this.answer];//9宫格绘制开始了,我们循环循环while (rndPos) {//根据返回的坐标,计算将要防止单元格的x坐标,y坐标var x =rndPos.coordinate[0] * this.var y =rndPos.coordinate[1] * this.var selected = false;//如果这个格子被选中,那么把选中集合内该值删除下(因为值可以重复...)tmpAnswer.forEach((item, i) => {if (item && item == rndPos.value) {selected = true;delete tmpAnswer[i]; //set undefined.}});//颜色配置下,可以配置选中,未选中var color = selected ? colors[2] : colors[1];//绘制矩形框了, 因为按照分组来绘制的,所以坐标按照组的中心点const rect = new fabric.Rect({width: this.height: this.originX: 'center',originY: 'center',fill: rndPos.value != '' ? colors[1] : colors[0],});//绘制值,就在正中间var text = new fabric.FabricText(rndPos.value, {fontSize: 30,originX: 'center',originY: 'center',fill: textColor,});//把矩形框和文字组合到一个组内var group = new fabric.Group([rect, text], {left: x,top: y,angle: 0,});//组的坐标可以进行设置的,我们按有无值配置为手,或者一般光标group.hoverCursor = rndPos.value != '' ? 'pointer' : 'default';//默认的选择框不需要,都配置下吧group.lockMovementX = true;group.lockMovementY = true;group.selectable = false;group.hasControls = false;group.subTargetCheck = false;//组可以设置任意属性,给它增加点额外的标签//这里把上面的矩形都加进去了,因为我没找到可以方便遍历儿子的属性...group.set({data: rndPos.value,selected: selected,index: rndPos.index,rect: rect,});//如果是选中,那么重新填充矩形框颜色,并且设定选定的图片if (selected) {rect.set({ fill: color });if (this.var newImg = this.newImg.set({originX: 'left',originY: 'top',left: group.left + this.top: group.top,angle: 20,});group.add(newImg);console.log('group=',group.data, rndPos.Index, newImg,group)}}//组对象增加到画布canvas.add(group);canvas.selectionColor = 'rgb(0,200,0)';canvas.selection = false;canvas.multiSelet = false;canvas.defaultCursor = 'pointer';//保持循环...rndPos = this.}//绘制canvas.renderAll();}
this.canvas.on('mouse:down', (options) => {//如果点击是组,那么我们就进行处理if (options.target && options.target.type == 'group') {var group = options.target;//selected属性是我们自定义属性,不是对象固有的。group.set({ selected: !group.get('selected') });var selected = group.get('selected');var val = group.get('data');var index = group.get('index');this.canvas.setActiveObject(group);this.if (val != '') {var color = selected ? colors[2] : colors[1];group.rect.set({ fill: color });if (this.if (selected) {var newImg = this.newImg.set({originX: 'left',originY: 'top',left: group.left + this.top: group.top,angle: 20,});group.add(newImg);this.group.left + 2.5,group.top - 2,group.width - 1,group.height + 2,]);} else {group.remove(this.this.}}}}});
fabric.FabricImage.fromURL(this.#loadImage).then((newImg) => {newImg.scale(0.1);//图片进行简单矩阵过滤,使之变红newImg.filters = [new fabric.filters.ColorMatrix({matrix: [0, 0, 0, 0, 255, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,],}),];newImg.applyFilters();this.#img = newImg;for (var i = 0; i < 9; i++) {this.#images[i] = newImg.cloneAsImage();}}).catch((err) => {console.log(err);});
5. 选中效果
// 定义动画函数var canvas = this.canvas;var l = rect[0];var t = rect[1];var w = rect[2];var h = rect[3];var step = 3;//清理之前的线段,加了自定义属性 flagvar arr = canvas.getObjects();for (var i = 0; i < arr.length; i++) {if (arr[i].id == 'flag') {canvas.remove(arr[i]);break;}}//这里之前是圆,不好看被替换后未改名称。var circle = new fabric.Rect({left: l,top: t,width: 10,height: 2,stroke: 'red',fill: 'red',id: 'flag',});canvas.add(circle);canvas.bringObjectToFront(circle);var currX = circle.left;var currY = circle.top;//运动算法实现,var animate = () => {currX = circle.left;currY = circle.top;if (currY <= t) {if (currX >= l + w - circle.width) {circle.set({left: l + w,top: currY + step,angle: 90,});} else {circle.set({left: currX + step,angle: 0,});}} else if (currX >= l + w) {if (currY >= t + h - circle.width) {circle.set({left: currX - step,angle: 180,top: t + h,});} else {circle.set({top: currY + step,});}} else if (currY >= t + h) {if (currX <= l + circle.width) {circle.set({top: t + h - circle.width,left: l,angle: 90,});} else {circle.set({left: currX - step,});}} else {if (currY <= t + circle.width) {circle.set({left: currX + step,angle: 0,top: t,});} else {circle.set({top: currY - step,});}}// 重新渲染画布canvas.renderAll();// 循环调用动画函数this.};// 启动动画animate();}
//移除动画#removeFlagLine() {fabric.util.cancelAnimFrame(this.#animateId);var arr = this.canvas.getObjects();for (var i = 0; i < arr.length; i++) {if (arr[i].id == 'flag') {this.canvas.remove(arr[i]);break;}}}
6. 汇总定义为游戏类
const colors = ['#e4e4e4', '#999', '#3a5985'];const textColor = '#fff';const bgColor = '#d5deef';const span = 10;class randomWordGame {#h = 0;#w = 0;#sw = 0;#sh = 0;#images = [];#loadImage = '';#game = ;#animateId = ;#img = ;constructor(divId, data, selectedValue, loadImage = '/imgs/selected.png') {this.data = data || [];this.answer = selectedValue || [];this.canvas = new fabric.Canvas(divId, {preserveObjectStacking: true,backgroundColor: bgColor,selectionColor: '#89AFE0',selectionLineWidth: 1,});this.#h = this.canvas.getHeight() - span * 4;this.#w = this.canvas.getWidth() - span * 4;this.#sh = this.#h / 3.0;this.#sw = this.#w / 3.0;this.#images = [];this.#loadImage = loadImage;this.#game = new coordinateGame(data, 9);this.#animateId = ;this.#img = ;this.canvas.on('mouse:down', (options) => {//...});//load images//fabric.FabricImage.fromURL(this.#loadImage)}init() {if (this.#img) {for (var i = 0; i < 9; i++) {this.#images[i] = this.#img.cloneAsImage();}}this.#game = new coordinateGame(this.data, 9);this.canvas.remove(...this.canvas.getObjects())}draw() {//...}//获取值#refreshValue() {}// 定义动画函数#animateDashedLine(rect) {}//移除动画#removeFlagLine() {}}
7. 应用和效果
import * as fabric from 'fabric';import randomWordGame from './randomWordGame';const data = ['1', 2, 2, '中华', '天', 15];const answer = ['1', '2', '中华'];var game = new randomWordGame('canvas', data, answer, '/imgs/selected.png');game.init();setTimeout(() => { game.draw();}, 1000);//game.draw();document.getElementById ("btn").addEventListener ("click", clickme, false);function clickme() {console.log(game.answer);}document.getElementById("btn2").addEventListener("click", clickme2, false);function clickme2() {game.data = ['我', 20, 2, '神么', '神', 15];game.answer = ['我'];game.init();game.draw();}
