问题
I'm trying to use a mousemove event on a canvas, inspired from Wayne's answer here, and the corresponding fiddle.
The problem is, I'm working with a code which creates "layered" canvas in the following way:
// canvas data layers
["marks", "foreground", "brushed", "highlight", "clickable_colors"].forEach(function(layer, i) {
canvas[layer] = selection
.append("canvas")
.attr({
id: layer, //added an id for easier selecting for mouse event
class: layer,
style: "z-index: " + i +10000000
})[0][0];
ctx[layer] = canvas[layer].getContext("2d");
});
My goal is to get the color which is on the layer "clickable_colors", and so I adapted the fiddle's script to set the mousemove event on that layer:
var my_clickable_canvas = document.getElementById('clickable_colors');
var context = my_clickable_canvas.getContext('2d');
context.fillStyle = "rgb(255,0,0)";
context.fillRect(0, 0, 50, 50);
context.fillStyle = "rgb(0,0,255)";
context.fillRect(55, 0, 50, 50);
$("#clickable_colors").mousemove(function(e) {
debugger;
var pos = findPos(this);
var x = e.pageX - pos.x;
console.log(x)
var y = e.pageY - pos.y;
var coord = "x=" + x + ", y=" + y;
var c = this.getContext('2d');
var p = c.getImageData(x, y, 1, 1).data;
var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
$('#examplecanvas').html(coord + "<br>" + hex);
console.log("hi")
});
However, the mousemove event isn't triggered at all. I first assumed it had to do with z-index, but giving it the highest value to make sure it's on top didn't solve it.
Does anyone know what might be preventing the event from firing? Or what I should pay attention to, given the "layered" formulation of canvases? Any help appreciated!
EDIT: Another explanation I found is that the "layered" canvases belong to a parent div element, whereas the working fiddle provides a canvas directly in the body. Could this be preventing the mouse event from working? If so, how to cope with canvases in a div?
EDIT2:
In response to a comment, a simplified and verifiable example is provided in this bl.ocks.
The original code is available here (cf. example file brushing.html
). The question is also discussed on the corresponding issue board.
回答1:
Multy layered canvas mouse events
The best way to approch is the simplest. Create just one mouse handler that listens to document mouse events. Then keep track of the active layer and handle it appropreatly.
The snippet is taken from another answer and adapted to create lines on several layers. Click the layer to select it, then click drag to add lines. Each layer has its own colour and needs to be selected to modify it.
There is only one mouse handler that listens to the document mouse events, the reset is done in the render code.
const ctx1 = canvas1.getContext("2d");
const ctx2 = canvas2.getContext("2d");
const ctx3 = canvas3.getContext("2d");
const ctx4 = canvas4.getContext("2d");
const Point2 = (x,y) => ({x,y}); // creates a point
const Line = (p1,p2) => ({p1,p2});
const setStyle = (style,ctx) => eachOf(Object.keys(style), key => { ctx[key] = style[key] } );
const eachOf = (array, callback) => {var i = 0; while (i < array.length && callback(array[i],i ++) !== true ); };
const list = {
items : null,
add(item) { this.items.push(item); return item },
eachItem(callback) {
var i = 0;
while(i < this.items.length){
callback(this.items[i],i++);
}
}
}
function createList(extend){
return Object.assign({},list,{items : []},extend);
}
// this will extend the points list
function getClosestPoint(from ,minDist) {
var closestPoint;
this.eachItem(point => {
const dist = Math.hypot(from.x - point.x, from.y - point.y);
if(dist < minDist){
closestPoint = point;
minDist = dist;
}
});
return closestPoint;
}
function distanceLineFromPoint(line,point,points){
const lx = points.items[line.p1].x;
const ly = points.items[line.p1].y;
const v1x = points.items[line.p2].x - lx;
const v1y = points.items[line.p2].y - ly;
const v2x = point.x - lx;
const v2y = point.y - ly;
// get unit dist of closest point
const u = (v2x * v1x + v2y * v1y)/(v1y * v1y + v1x * v1x);
if(u >= 0 && u <= 1){ // is the point on the line
return Math.hypot(lx + v1x * u - point.x, ly + v1y * u - point.y);
} else if ( u < 0 ) { // point is before start
return Math.hypot(lx - point.x, ly - point.y);
}
// point is after end of line
return Math.hypot(points.items[line.p2].x - point.x, points.items[line.p2].y - point.y);
}
// this will extend the lines list
function getClosestline(from ,minDist) {
var closestLine;
this.eachItem(line => {
const dist = distanceLineFromPoint(line,from,this.points);
if(dist < minDist){
closestLine = line;
minDist = dist;
}
});
return closestLine;
}
function drawPoint(point,ctx){
ctx.moveTo(point.x,point.y);
ctx.rect(point.x - 2,point.y - 2, 4,4);
}
function drawLine(line,ctx,points){
ctx.moveTo(points.items[line.p1].x,points.items[line.p1].y);
ctx.lineTo(points.items[line.p2].x,points.items[line.p2].y);
}
function drawLines(ctx){ this.eachItem(line => drawLine(line,ctx,this.points)) }
function drawPoints(ctx){this.eachItem(point => drawPoint(point,ctx)) }
const mouse = {x : 0, y : 0, button : false, drag : false, dragStart : false, dragEnd : false, dragStartX : 0, dragStartY : 0}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
const lb = mouse.button;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
if(lb !== mouse.button){
if(mouse.button){
mouse.drag = true;
mouse.dragStart = true;
mouse.dragStartX = mouse.x;
mouse.dragStartY = mouse.y;
}else{
mouse.drag = false;
mouse.dragEnd = true;
}
}
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
// short cut vars
var w;
var h;
var cw; // center
var ch;
var globalTime;
var closestLine;
var closestPoint;
var pointDrag; // true is dragging a point else dragging a line
var dragOffsetX;
var dragOffsetY;
var cursor;
var toolTip;
var helpCount = 0;
const minDist = 20;
var points ;
var lines;
const layers = [{
lineStyle : { lineWidth : 2, strokeStyle : "green", fillStyle : "Green" },
pointStyle : { lineWidth : 1, strokeStyle : "green", fillStyle : "Green"},
font : { fillStyle : "green", font : "18px arial", textAlign : "left"},
context : ctx1,
canvas : canvas1,
points :(points = createList({ getClosest : getClosestPoint,draw : drawPoints})),
lines : createList({getClosest : getClosestline, draw : drawLines, points : points }),
ready : false,
},{
lineStyle : { lineWidth : 2, strokeStyle : "blue", fillStyle : "blue" },
pointStyle : { lineWidth : 1, strokeStyle : "blue", fillStyle : "blue"},
font : { fillStyle : "blue", font : "18px arial", textAlign : "left"},
context : ctx2,
canvas : canvas2,
points :(points = createList({ getClosest : getClosestPoint,draw : drawPoints})),
lines : createList({getClosest : getClosestline, draw : drawLines, points : points}),
ready : false,
},{
lineStyle : { lineWidth : 2, strokeStyle : "gold", fillStyle : "gold" },
pointStyle : { lineWidth : 1, strokeStyle : "gold", fillStyle : "gold"},
font : { fillStyle : "gold", font : "18px arial", textAlign : "left"},
context : ctx3,
canvas : canvas3,
points :(points = createList({ getClosest : getClosestPoint,draw : drawPoints})),
lines : createList({getClosest : getClosestline, draw : drawLines, points : points}),
ready : false,
},{
lineStyle : { lineWidth : 2, strokeStyle : "aqua", fillStyle : "aqua" },
pointStyle : { lineWidth : 1, strokeStyle : "aqua", fillStyle : "aqua"},
font : { fillStyle : "aqua", font : "18px arial", textAlign : "left"},
context : ctx4,
canvas : canvas4,
points : (points = createList({ getClosest : getClosestPoint,draw : drawPoints})),
lines : createList({getClosest : getClosestline, draw : drawLines, points : points}),
ready : false,
}
];
var currentLayer = 0;
const highlightStyle = {
lineWidth : 3,
strokeStyle : "red",
}
const font = {
font : "18px arial",
fillStyle : "black",
textAlign : "center",
}
// main update function
function update(timer){
if(mouse.button){
if(mouse.x < 50 && mouse.y < 28 *5){
mouse.drag=mouse.button=mouse.dragStart = false;
currentLayer = (mouse.y / 28)|0;
eachOf(layers,layer=>layer.ready = false);
}
}
const layer = layers[currentLayer];
const ctx = layer.context;
const canvas = layer.canvas;
const lines = layer.lines;
const points = layer.points;
const lineStyle = layer.lineStyle;
const pointStyle = layer.pointStyle;
cursor = "crosshair";
toolTip = helpCount < 2 ? "Click drag to create a line" : "";
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if(w !== innerWidth || h !== innerHeight){
cw = (w = canvas1.width = innerWidth) / 2;
ch = (h = canvas1.height = innerHeight) / 2;
canvas2.height = canvas3.height = canvas4.height = h;
canvas2.width = canvas3.width = canvas4.width = w;
eachOf(layers,layer=>layer.ready = false);
}else{
ctx.clearRect(0,0,w,h);
}
if(mouse.drag=== false){
if(mouse.x < 50 && mouse.y < 28 *5){
cursor = "pointer";
toolTip = "Click to select layer";
closestPoint = closestLine = undefined;
}else{
closestLine = undefined;
closestPoint = points.getClosest(mouse,minDist);
if(closestPoint === undefined){
closestLine = lines.getClosest(mouse,minDist);
}
if(closestPoint || closestLine){
toolTip = "Click drag to move " + (closestPoint ? "point" : "line");
cursor = "move";
}
}
}
if(mouse.dragStart){
if(closestPoint){
dragOffsetX = closestPoint.x - mouse.x;
dragOffsetY = closestPoint.y - mouse.y;
pointDrag = true;
}else if( closestLine){
dragOffsetX = points.items[closestLine.p1].x - mouse.x;
dragOffsetY = points.items[closestLine.p1].y - mouse.y;
pointDrag = false;
} else {
points.add(Point2(mouse.x,mouse.y));
closestPoint = points.add(Point2(mouse.x,mouse.y));
closestLine = lines.add(Line(points.items.length-2,points.items.length-1));
dragOffsetX = 0;
dragOffsetY = 0;
pointDrag = true;
helpCount += 1;
}
mouse.dragStart = false;
}else if(mouse.drag){
cursor = 'none';
if(pointDrag){
closestPoint.x = mouse.x + dragOffsetX;
closestPoint.y = mouse.y + dragOffsetY;
}else{
const dx = mouse.x- mouse.dragStartX;
const dy = mouse.y -mouse.dragStartY;
mouse.dragStartX = mouse.x;
mouse.dragStartY = mouse.y;
points.items[closestLine.p1].x += dx;
points.items[closestLine.p1].y += dy;
points.items[closestLine.p2].x += dx;
points.items[closestLine.p2].y += dy;
}
}else{
}
// draw all points and lines
setStyle(lineStyle,ctx);
ctx.beginPath();
lines.draw(ctx);
ctx.stroke();
setStyle(pointStyle,ctx);
ctx.beginPath();
points.draw(ctx);
ctx.stroke();
// draw highlighted point or line
setStyle(highlightStyle,ctx);
ctx.beginPath();
if(closestLine){ drawLine(closestLine,ctx,points) }
if(closestPoint){ drawPoint(closestPoint, ctx) }
ctx.stroke();
eachOf(layers,(layer,i)=>{
if(!layer.ready){
const ctx = layer.context;
ctx.globalAlpha = 0.75;
ctx.clearRect(0,0,w,h);
setStyle(layer.lineStyle,ctx);
ctx.beginPath();
layer.lines.draw(ctx);
ctx.stroke();
setStyle(layer.pointStyle,ctx);
ctx.beginPath();
layer.points.draw(ctx);
ctx.stroke();
setStyle(layer.font,ctx);
ctx.fillText("Layer " + (i + 1), 10, i * 28+28);
layer.ready = true;
ctx.globalAlpha = 1;
}
});
setStyle(layer.font,ctx);
ctx.fillText("Layer On" , 10,currentLayer * 28+28);
if(helpCount < 2){
setStyle(font,ctx);
ctx.fillText(toolTip,cw,30);
}
canvas4.style.cursor = cursor;
if(helpCount < 5){
canvas4.title = toolTip;
}else{
canvas4.title = "Click layer to select it";
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>
<canvas id="canvas3"></canvas>
<canvas id="canvas4"></canvas>
来源:https://stackoverflow.com/questions/44924217/multiple-canvas-layers-and-mousemove