仍然是 在图片上特定区域根据数值显示不同的颜色 的需求,过了这么久,svg图迟迟提供不了,考虑canvas方案。记录比较下canvas及各canvas框架的使用。
canvas
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>使用 JavaScript 在图像上选择区域</title>
</head>
<body>
<div>
<canvas id="canvas1"></canvas>
<script>
{
const canvas = document.getElementById("canvas1");
const ctx = canvas.getContext("2d");
const image = new Image();
image.src = "../images/1.jpg";
image.onload = function () {
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
};
let startX;
let startY;
let width;
let height;
canvas.addEventListener("mousedown", function (event) {
startX = event.offsetX;
startY = event.offsetY;
});
canvas.addEventListener("mouseup", function (event) {
if (startX !== undefined && startY !== undefined) {
width = event.offsetX - startX;
height = event.offsetY - startY;
ctx.beginPath();
ctx.rect(startX, startY, width, height);
ctx.strokeStyle = "#FF0000";
ctx.stroke();
console.log(`选中区域的位置为 (${startX}, ${startY}),大小为 ${width} × ${height}`);
}
startX = undefined;
startY = undefined;
width = undefined;
height = undefined;
});
}
</script>
</div>
</body>
</html>
使用鼠标在图片上拖动来绘制矩形及其他图形区域
annotorious.js
https://github.com/annotorious/annotorious<link rel="stylesheet" href="../plugins/annotorious-2.7.13/annotorious.min.css" />
<script src="../plugins/annotorious-2.7.13/annotorious.min.js"></script>
<script src="../plugins/annotorious-2.7.13/annotorious-toolbar.min.js"></script>
<style>
#hallstatt {
border: 1px solid red;
}
</style>
<div id="toolbar"></div>
<img id="hallstatt" src="../images/1.jpg" />
<script>
var sampleAnnotation = {
"@context": "http://www.w3.org/ns/anno.jsonld",
id: "#a88b22d0-6106-4872-9435-c78b5e89fede",
type: "Annotation",
body: [
{
type: "TextualBody",
value: "It's Hallstatt in Upper Austria",
},
{
type: "TextualBody",
purpose: "tagging",
value: "Hallstatt",
},
{
type: "TextualBody",
purpose: "tagging",
value: "Upper Austria",
},
],
target: {
selector: {
type: "FragmentSelector",
conformsTo: "http://www.w3.org/TR/media-frags/",
value: "xywh=pixel:421,80,151,151",
},
},
};
window.onload = function () {
var anno = Annotorious.init({
image: "hallstatt",
locale: "auto",
allowEmpty: true,
});
Annotorious.Toolbar(anno, document.getElementById("toolbar"));
//可以标注矩形和多边形区域,不能标注圆形区域,且添加的注释未显示在图片上
anno.on("createAnnotation", function (annotation) {
console.log(annotation.target, annotation.body);
});
};
</script>
fabric.js
<script src="../plugins/fabric.min.js"></script>
<link href="../plugins/bootstrap-5.1.3/css/bootstrap.min.css" rel="stylesheet" />
<script src="../plugins/jquery/jquery-3.3.1.js"></script>
<script src="../plugins/bootstrap-5.1.3/js/bootstrap.bundle.min.js"></script>
<body>
<div class="row g-2">
<div class="col-8" style="overflow: auto">
<canvas id="example" style="border: solid 1px #ccc"></canvas>
<canvas id="example_re" style="border: solid 1px #ccc"></canvas>
</div>
<div class="col-4">
<ul class="nav nav-tabs mt-2" id="myTab">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" data-bs-target="#shape-rect" style="cursor: pointer">矩形</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" data-bs-target="#shape-circle" style="cursor: pointer">圆形</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade active show" id="shape-rect" role="tabpanel">
<div class="row g-2">
<div class="col-12">
<div class="input-group">
<span class="input-group-text">Left</span>
<input type="number" class="form-control" value="0" onchange="setProp(this,'rect','left')" />
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">Top</span>
<input type="number" class="form-control" value="0" onchange="setProp(this,'rect','top')" />
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">Width</span>
<input type="number" class="form-control" value="80" onchange="setProp(this,'rect','width')" />
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">Height</span>
<input type="number" class="form-control" value="50" onchange="setProp(this,'rect','height')" />
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">Name</span>
<input type="text" class="form-control" value="" onchange="setProp(this,'rect','name')" />
</div>
</div>
</div>
<button class="btn btn-secondary mt-2" onclick="addRect()">新增矩形</button>
</div>
<div class="tab-pane fade" id="shape-circle" role="tabpanel">
<div class="row g-2">
<div class="col-12">
<div class="input-group">
<span class="input-group-text">Left</span>
<input type="number" class="form-control" value="0" onchange="setProp(this,'circle','left')" />
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">Top</span>
<input type="number" class="form-control" value="0" onchange="setProp(this,'circle','top')" />
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">radius</span>
<input type="number" class="form-control" value="50" onchange="setProp(this,'circle','radius')" />
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">LineWidth</span>
<input type="number" class="form-control" value="5" onchange="setProp(this,'circle','strokeWidth')" />
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">Name</span>
<input type="text" class="form-control" value="" onchange="setProp(this,'circle','name')" />
</div>
</div>
</div>
<button class="btn btn-secondary mt-2" onclick="addCircle()">新增圆形</button>
</div>
</div>
<hr />
<button class="btn btn-secondary" onclick="cloneShape()">复制图形</button>
<button class="btn btn-secondary" onclick="delShape()">删除图形</button>
<button class="btn btn-secondary mt-2" onclick="getAllShape()">预览</button>
<hr />
<div>共<span id="info">0</span>个</div>
</div>
</div>
<script>
const canvas = new fabric.Canvas("example");
//背景图
canvas.setBackgroundImage("../images/2.jpg", canvas.renderAll.bind(canvas));
const canvas_re = new fabric.StaticCanvas("example_re");
canvas_re.setBackgroundImage("../images/2.jpg", canvas_re.renderAll.bind(canvas_re));
//通用属性
const default_prop = {
transparentCorners: false, //选中时 控制手柄的样式
borderColor: "green",
cornerColor: "green",
cornerSize: 5,
lockRotation: true, //禁止旋转
lockScalingFlip: true, //禁止缩放时翻转
lockSkewingX: true, //禁止水平方向扭曲
lockSkewingY: true, //禁止垂直方向扭曲
};
const default_vals_rect = [0, 0, 80, 50, ""];
const default_vals_circle = [0, 0, 50, 5, ""];
// 创建对象
const bg_img = new Image();
bg_img.src = "../images/2.jpg";
bg_img.onload = function () {
//将canvas的宽高设置为背景图片的宽高
canvas.setWidth(bg_img.width);
canvas.setHeight(bg_img.height);
canvas_re.setWidth(bg_img.width);
canvas_re.setHeight(bg_img.height);
};
//根据参数值或者表单值添加矩形区域
function addRect(vals = [], is_re = false) {
if (vals.length < 1) {
$("#shape-rect input").each((idx, elem) => {
if (idx < 4) {
let value = parseInt(elem.value.trim());
if (isNaN(value)) {
elem.value = 10;
value = 10;
}
vals.push(value);
} else {
let value = elem.value.trim();
vals.push(value);
}
});
}
const rect = new fabric.Rect(
Object.assign(
{
left: vals[0],
top: vals[1],
originX: "left",
originY: "top",
width: vals[2],
height: vals[3],
fill: "red",
},
default_prop
)
);
rect["name"] = vals[4];
cv = !is_re ? canvas : canvas_re;
cv.add(rect);
bindEvent(rect);
}
//根据参数值或者表单值添加圆形区域
function addCircle(vals = [], is_re = false) {
if (vals.length < 1) {
$("#shape-circle input").each((idx, elem) => {
if (idx < 4) {
let value = parseInt(elem.value.trim());
if (isNaN(value)) {
elem.value = 10;
value = 10;
}
vals.push(value);
} else {
let value = elem.value.trim();
vals.push(value);
}
});
}
const circle = new fabric.Circle(
Object.assign(
{
left: vals[0],
top: vals[1],
radius: vals[2],
fill: "transparent",
strokeWidth: vals[3],
stroke: "red",
lockScalingX: true,
lockScalingY: true,
lockScaling: true,
},
default_prop
)
);
circle["name"] = vals[4];
cv = !is_re ? canvas : canvas_re;
cv.add(circle);
bindEvent(circle);
}
//当单个对象选中或者修改时,将数值同步到表单区域
function bindEvent(obj) {
obj.on("selected", function () {
setFormVal(obj);
});
obj.on("modified", function () {
setFormVal(obj);
});
}
//将当前对象的各项数值同步到表单区域
function setFormVal(obj) {
if (obj.type == "rect") {
const triggerEl = document.querySelector(`#myTab a[data-bs-target="#shape-${obj.type}"]`);
bootstrap.Tab.getOrCreateInstance(triggerEl).show();
const vals = [obj.get("left"), obj.get("top"), obj.getScaledWidth(), obj.getScaledHeight(), obj.name];
$("#shape-rect input").each((idx, elem) => {
elem.value = vals[idx];
});
} else if (obj.type == "circle") {
const triggerEl = document.querySelector(`#myTab a[data-bs-target="#shape-${obj.type}"]`);
bootstrap.Tab.getOrCreateInstance(triggerEl).show();
const vals = [obj.get("left"), obj.get("top"), obj.get("radius"), obj.get("strokeWidth"), obj.name];
$("#shape-circle input").each((idx, elem) => {
elem.value = vals[idx];
});
}
}
//将表单数值的变化 同步到canvas上
function setProp(this_obj, shape, prop) {
const active_objs = canvas.getActiveObjects();
if (active_objs.length > 1) return;
const active_obj = active_objs[0];
if (active_obj instanceof fabric.Object) {
const obj = {};
obj[prop] = parseInt(this_obj.value);
active_obj.set(obj);
canvas.renderAll();
}
}
//复制选中的对象
function cloneShape() {
let pos_x = 0;
let pos_y = 0;
//选择多个对象时需要重新计算坐标位置
if (canvas.getActiveObject().type == "activeSelection") {
const activeSelection = canvas.getActiveObject();
pos_x = (activeSelection.left + (activeSelection.left + activeSelection.width)) / 2;
pos_y = (activeSelection.top + (activeSelection.top + activeSelection.height)) / 2;
}
const active_objs = canvas.getActiveObjects();
for (let i in active_objs) {
const active_obj = active_objs[i];
if (active_obj instanceof fabric.Object) {
const left = pos_x + active_obj.get("left") + 20;
const top = pos_y + active_obj.get("top") + 20;
active_obj.clone((clone) => {
Object.assign(clone, default_prop);
clone["name"] = active_obj.name;
clone["left"] = left;
clone["top"] = top;
if (active_obj.type == "circle") {
clone["lockScalingX"] = true;
clone["lockScalingY"] = true;
clone["lockScaling"] = true;
}
canvas.add(clone);
bindEvent(clone);
});
}
}
}
//删除选中的对象
function delShape() {
const active_objs = canvas.getActiveObjects();
for (let i in active_objs) {
const active_obj = active_objs[i];
if (active_obj instanceof fabric.Object) {
canvas.remove(active_obj);
}
}
}
//获取canvas上的所有对象数据,在另外的canvas上重绘预览
function getAllShape() {
canvas.discardActiveObject();
const data = [];
const all_obj = canvas.getObjects();
for (let i in all_obj) {
const obj = all_obj[i];
if (obj.type == "rect") {
data.push({
name: obj.name,
type: obj.type,
left: obj.get("left"),
top: obj.get("top"),
width: obj.getScaledWidth(),
height: obj.getScaledHeight(),
});
} else if (obj.type == "circle") {
data.push({
name: obj.name,
type: obj.type,
left: obj.get("left"),
top: obj.get("top"),
radius: obj.get("radius"),
strokeWidth: obj.get("strokeWidth"),
});
}
}
console.log("data", data);
document.querySelector("#info").innerHTML = data.length;
canvas_re.clear(); //清除所有(包括背景图)
canvas_re.setBackgroundImage("../images/2.jpg", canvas_re.renderAll.bind(canvas_re));
for (let i in data) {
const obj = data[i];
if (obj.type == "rect") {
addRect([obj.left, obj.top, obj.width, obj.height, obj.name], true);
} else if (obj.type == "circle") {
addCircle([obj.left, obj.top, obj.radius, obj.strokeWidth, obj.name], true);
}
}
// console.log(obj.type, obj.get("width"), obj.get("height"), obj.get("left"), obj.get("top"));
// console.log(obj.getCenterPoint(), obj.getCoords(), obj.getBoundingRect(), obj.getObjectScaling(), obj.getPointByOrigin("left", "top"));
// console.log(obj.getScaledHeight(), obj.getScaledWidth(), obj.id, obj.name);
}
//选中事件
canvas.on("selection:created", function (opt) {
console.log("selection:created", opt);
//当选中多个对象时 不同步表单数据,只显示默认值
if (opt.selected.length > 1) {
$("#shape-rect input").each((idx, elem) => {
elem.value = default_vals_rect[idx];
});
$("#shape-circle input").each((idx, elem) => {
elem.value = default_vals_circle[idx];
});
}
});
//取消选中事件
canvas.on("selection:cleared", function (opt) {
$("#shape-rect input").each((idx, elem) => {
elem.value = default_vals_rect[idx];
});
$("#shape-circle input").each((idx, elem) => {
elem.value = default_vals_circle[idx];
});
});
// 监听键盘事件,主要用于对齐位置
document.addEventListener("keydown", function (event) {
const active_objs = canvas.getActiveObjects();
for (let i in active_objs) {
const active_obj = active_objs[i];
if (active_obj instanceof fabric.Object) {
switch (event.keyCode) {
case 37: // 左键
active_obj.set({ left: active_obj.get("left") - 1 });
// canvas.renderAll();
break;
case 38: // 上键
active_obj.set({ top: active_obj.get("top") - 1 });
// canvas.renderAll();
break;
case 39: // 右键
active_obj.set({ left: active_obj.get("left") + 1 });
// canvas.renderAll();
break;
case 40: // 下键
active_obj.set({ top: active_obj.get("top") + 1 });
// canvas.renderAll();
break;
default:
break;
}
}
}
canvas.renderAll();
});
</script>
</body>
标签:canvas,obj,fabric,value,js,vals,active,const,图片 From: https://www.cnblogs.com/caroline2016/p/18084932