序言
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;
//清理之前的线段,加了自定义属性 flag
var 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();
}