I'm currently working on an automated tool that reproduce hand-drawing previously recorded on a canvas. I'm using fabric.js to create a canvas and free draw on it.
In this part of the application, the user can't draw anything but is watching the canvas being drawn on. That's why the canvas is behind an invisible div that blocks all interactions.
So far, I managed to recreate the drawing process using an asynchronous function and by triggering well-timed mouse events at specific locations.
Let's start with some mouse coordinates to use for the path to draw :
var coord = [
{x: 927, y: 115},
{x: 925, y: 116},
{x: 922, y: 116},
{x: 910, y: 115},
{x: 899, y: 114}
]
I use a custom function that simulates a mouse event at a specific location :
function simulateMouseEvent(e, eventName, x, y) {
e.dispatchEvent(new MouseEvent(eventName, {
view: window,
bubbles: true,
cancelable: true,
clientX: x,
clientY: y,
button: 0
}));
}
To start, I trigger a mousedown event as if the user was clicking on the canvas :
simulateMouseEvent(canvasElement, 'mousedown', coord[0].x, coord[0].y);
Then a for loop launches a bunch of consecutive mousemove events every 200 ms like so :
for (i = 1; i < coord.length; i++) {
await movement(coord[i]);
}
function movement(coordinates) {
return new Promise(function(resolve, reject) {
simulateMouseEvent(canvasElement, 'mousemove', coordinates.x, coordinates.y);
setTimeout(function() {
resolve('');
}, 200);
})
}
Finally, I stop the drawing by triggering a mouseup event like so :
var last = coord[coord.length - 1];
simulateMouseEvent(canvasElement, 'mouseup' last.x, last.y);
So everything works fine if the user's cursor is not above the canvas (which is what happens most of the time) even though there is a mask theoretically blocking the user's interaction with the canvas.
I believe that triggering a mouse event on a div "activates" it even if that div can't be reached. I tried adding pointer-events: none on the css rule of the canvas wih no difference. Is there a way to block real mouse cursor events while triggering fake ones ?
Related
The game I'm creating doesn't require any physics, however you are able to interact when hovering over/clicking on the sprite by using sprite.setInteractive({cursor: "pointer"});, sprite.on('pointermove', function(activePointer) {...}); and similar. However I noticed two issues with that:
The sprite has some area which are transparent. The interactive functions will still trigger when clicking on those transparent areas, which is unideal.
When playing a sprite animation, the interactive area doesn't seem to entirely (at all?) change, thus if the sprite ends on a frame bigger than the previous, there end up being small areas I can't interact with.
One option I thought of was to create a polygon over my sprite, which covers the area I want to be interactive. However before I do that, I simply wanted to ask if there are simpler ways to fix these issues.
Was trying to find an answer for this myself just now..
Think Make Pixel Perfect is what you're looking for.
this.add.sprite(x, y, key).setInteractive(this.input.makePixelPerfect());
https://newdocs.phaser.io/docs/3.54.0/focus/Phaser.Input.InputPlugin-makePixelPerfect
This might not be the best solution, but I would solve this problem like this. (If I don't want to use physics, and if it doesn't impact the performance too much)
I would check in the event-handler, if at the mouse-position the pixel is transparent or so, this is more exact and less work, than using bounding-boxes.
You would have to do some minor calculations, but it should work well.
btw.: if the origin is not 0, you would would have to compensate in the calculations for this. (in this example, the origin offset is implemented)
Here is a demo, for the click event:
let Scene = {
preload ()
{
this.load.spritesheet('brawler', 'https://labs.phaser.io/assets/animations/brawler48x48.png', { frameWidth: 48, frameHeight: 48 });
},
create ()
{
// Animation set
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 0, 1, 2, 3 ] }),
frameRate: 8,
repeat: -1
});
// create sprite
const cody = this.add.sprite(200, 100).setOrigin(0);
cody.play('walk');
cody.setInteractive();
// just info text
this.mytext = this.add.text(10, 10, 'Click the Sprite, or close to it ...', { fontFamily: 'Arial' });
// event to watch
cody.on('pointerdown', function (pointer) {
// calculate x,y position of the sprite to check
let x = (pointer.x - cody.x) / (cody.displayWidth / cody.width)
let y = (pointer.y - cody.y) / (cody.displayHeight / cody.height);
// just checking if the properties are set
if(cody.anims && cody.anims.currentFrame){
let currentFrame = cody.anims.currentFrame;
let pixelColor = this.textures.getPixel(x, y, currentFrame.textureKey, currentFrame.textureFrame);
// alpha > 0 a visible pixel of the sprite, is clicked
if(pixelColor.a > 0) {
this.mytext.text = 'Hit';
} else {
this.mytext.text = 'No Hit';
}
// just reset the textmessage
setTimeout(_ => this.mytext.text = 'Click the Sprite, or close to it ...' , 1000);
}
}, this);
}
};
const config = {
type: Phaser.AUTO,
width: 400,
height: 200,
scene: Scene
};
const game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I am currently implementing a functionality to place an canvas over pdf in pdf-reader so that we can draw on top of canvas, so the pdf in which canvas is placed can be zoomed-in and out to much extent, and I have to ensure that canvas should also resize as per the pdf, so that drawing on top of it remains intact, for resizing canvas I am using the canvas.style.width , drawing.style.height to resize it when pdf-resizes.
But now my concern is that on resizing mouse-pointer changes its position, for instance in the attached image my mouse is in bottom-right and it draws in top-left, due to shifting in mouse coordinates.
I am using RxJS for drawing, and capturing events, but I am not been able to figure out how to take care of mouse position in summary, I want coordinates to scale as per canvas resize.
private captureEvents(canvasEl: HTMLCanvasElement) {
// this will capture all mousedown events from the canvas element
this.drawingSubscription = fromEvent(canvasEl, 'mousedown')
.pipe(
switchMap((e) => {
// after a mouse down, we'll record all mouse moves
return fromEvent(canvasEl, 'mousemove')
.pipe(
// we'll stop (and unsubscribe) once the user releases the mouse
// this will trigger a 'mouseup' event
takeUntil(fromEvent(canvasEl, 'mouseup').do((event: WheelEvent) => {
console.log('e', e.type, e);
const prevPos = {
x: null,
y: null
};
this.coordinatesArray[this.file.current_slide - 1].push(prevPos);
console.log('coordinatesArray', this.coordinatesArray);
})),
// we'll also stop (and unsubscribe) once the mouse leaves the canvas (mouseleave event)
takeUntil(fromEvent(canvasEl, 'mouseleave')),
// pairwise lets us get the previous value to draw a line from
// the previous point to the current point
pairwise()
)
})
)
.subscribe((res: [MouseEvent, MouseEvent]) => {
const rect = canvasEl.getBoundingClientRect();
// previous and current position with the offset
const prevPos = {
x: res[0].clientX - rect.left,
y: res[0].clientY - rect.top
};
const currentPos = {
x: res[1].clientX - rect.left,
y: res[1].clientY - rect.top
};
this.coordinatesArray[this.file.current_slide - 1].push(prevPos);
// console.log('coordinatesArray', this.coordinatesArray);
this.drawOnCanvas(prevPos, currentPos);
});
}
Moreover, I am not generating the new canvas on every zoom-in/out, I am just modifying the it's height/width as per dimensions of page on top of which it is present.
I eventually solved it after spending a hell lot of time.
I have posted the answer in relevant thread that will be helpful to others going through the similar problem.
https://stackoverflow.com/a/56363476/5763627
I would like selectable objects to snap their center to my mouse cursor on click. The only modification allowed to the user in this case is moving the object, no scaling, rotating, etc. Simply updating the position of the object on mousedown or selected will update its position only until the moving event is fired, where the object will snap to its original position and then begin following the mouse.
rect.on('moving', moveHandler);
function moveHandler(evt) {
var mousePnt = $canvas.getPointer(evt.e);
rect.set({
left:mousePnt.x - rect.width*0.5 , top:mousePnt.y - rect.height*0.5});
this.setCoords();
}
This is what I've come up with to center a selectable rectangle on the cursor, but I'm certain it's firing two movement events. Is there a way to override that original positioning. Or should I instead write my own mousedown, mouseup, and moving listeners to mimic the default dragging behavior?
You solution is fine if no rotating or scaling is involved.
You are not executing anything more than necessary if not the .setCoords that during a movement is optional since it will be called on mouseUp when the translation is finished.
If you want to take the mouseDown approach, changing the position once for all, you can use this logic:
var canvas = new fabric.Canvas('c');
canvas.add(new fabric.Rect({width: 50, height: 50}));
canvas.on('mouse:down', function(opt) {
if (this._currentTransform && opt.target) {
var target = opt.target;
target.top = this._currentTransform.ey - target.height/2;
target.left = this._currentTransform.ex - target.width/2;
this._currentTransform.left = target.left;
this._currentTransform.offsetX = this._currentTransform.ex - target.left;
this._currentTransform.offsetY = this._currentTransform.ey - target.top;
this._currentTransform.top = target.top;
this._currentTransform.original.left = target.left;
this._currentTransform.original.top = target.top;
this.renderAll();
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.18/fabric.min.js"></script>
<canvas id="c" ></canvas>
You have to modify all the transform information that fabric noted down on the mouseDown event before the your mousedown handler is executed.
Hi I know we can trigger click event . but I want to know that can we trigger mousemove event without any actual mouse movement by user.
Description :
I want to show a message when user select something. on canvas ,my canvas is of full height and width,when user click on a button the canvas shows up. when user do mouse movement he see a message "Click and drag on any part of the web page". this message follows the mouse movement of the user.
What I want to do :
When user click the button he should see the message that "Click and drag on any part of the web page". and message must follow wherever user moves the mouse.
Problem :
User is not able to see the message after click until he/she moves his mouse.
Code:
function activateCanvas() {
var documentWidth = jQ(document).width(),
documentHeight = jQ(document).height();
jQ('body').prepend('<canvas id="uxa-canvas-container" width="' + documentWidth + '" height="' + documentHeight + '" ></canvas><form method="post" id="uxa-annotations-container"></form>');
canvas = new UXAFeedback.Canvas('uxa-canvas-container', {
containerClass: 'uxa-canvas-container',
selection: false,
defaultCursor: 'crosshair'
});
jQ(function() {
var canvas = jQ('.upper-canvas').get(0);
var ctx = canvas.getContext('2d');
var x,y;
var tooltipDraw = function(e) {
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore();
x = e.pageX - canvas.offsetLeft;
y = e.pageY - canvas.offsetTop;
var str = 'Click and drag on any part of the webpage.';
ctx.fillStyle = '#ddd';
ctx.fillRect(x + 10, y - 60, 500, 40);
ctx.fillStyle = 'rgb(12, 106, 185)';
ctx.font = 'bold 24px verdana';
ctx.fillText(str, x + 20, y - 30, 480);
};
canvas.addEventListener('onfocus',tooltipDraw,0);
canvas.addEventListener('mousemove',tooltipDraw,0);
canvas.addEventListener('mousedown', function() {
canvas.removeEventListener('mousemove', tooltipDraw, false);
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore();
}, false);
});
}
jQ('body').on('click', '.mood_img_div', function() {
// alert("soemthing");
toggleOverlay();
activateCanvas();
});
I have made a function which is called after click but the message is not visible. Is there any way to call it for the first time with message and show is everytime when user uses mouse.
I have replaced jQuery with jQ because I am making my own plugin(this is not causing the problem)
A good native approach is to use dispatchEvent method on EventTarget.
It dispatches an Event at the specified EventTarget, invoking the affected EventListeners in the appropriate order. The normal event processing rules (including the capturing and optional bubbling phase) also apply to events dispatched manually with dispatchEvent().
Try
// 1. Add an event listener first
canvas.addEventListener('mousemove', tooltipDraw ,0);
// 2. Trigger this event wherever you wish
canvas.dispatchEvent(new Event('mousemove'));
in your case it should trigger mousemove event on canvas element.
(Triggering events in vanilla JavaScript article can be also useful):
var elem = document.getElementById('elementId');
elem.addEventListenter('mousemove', function() {
// Mousemove event callback
}, 0);
var event = new Event('mousemove'); // (*)
elem.dispatchEvent(event);
// Line (*) is equivalent to:
var event = new Event(
'mousemove',
{ bubbles: false, cancelable: false });
jQuery:
Try this with jQuery trigger method:
$('body').bind('mousemove',function(e){
// Mousemove event triggered!
});
$(function(){
$('body').trigger('mousemove');
});
OR (if you need triggering with coords)
event = $.Event('mousemove');
// coordinates
event.pageX = 100;
event.pageY = 100;
// trigger event
$(document).trigger(event);
OR
Try using .mousemove() jQuery method
let coordX = 0; // Moving from the left side of the screen
let coordY = window.innerHeight / 2; // Moving in the center
function move() {
// Move step = 20 pixels
coordX += 20;
// Create new mouse event
let ev = new MouseEvent("mousemove", {
view: window,
bubbles: true,
cancelable: true,
clientX: coordX,
clientY: coordY
});
// Send event
document.querySelector('Put your element here!').dispatchEvent(ev);
// If the current position of the fake "mouse" is less than the width of the screen - let's move
if (coordX < window.innerWidth) {
setTimeout(() => {
move();
}, 10);
}
}
// Starting to move
move();
Albeit it is probably possible to mimic such an event as shown in Andrii Verbytskyi's answer, most of the time, when you want to do it, it is because of an "X-Y problem".
If we take OP's case for instance, here we absolutely don't need to trigger this mousemove event.
Pseudo-code of current implementation :
function mousemoveHandler(evt){
do_something_with(evt.pageX, e.pageY);
}
element.addEventListener('mousemove', mousemoveHandler)
function clickHandler(evt){
do_something_else();
}
element.addEventListener('click', clickHandler);
And what we want is to also call do_something_with in the click handler.
So OP spends some time to find a way to trigger a fake mousemove, spends another amount of time trying to implement it, while all that is needed is to add a call to do_something_with in clickHandler.
Both mousemove and click events have these pageX and pageY properties, so the event can be passed has is, but in other case, we could also just want to pass it with a fake object containing required properties.
function mousemoveHandler(evt){
do_something_with(evt.pageX, evt.pageY);
}
element.addEventListener('mousemove', mousemoveHandler)
function clickHandler(evt){
do_something_else();
do_something_with(evt.pageX, evt.pageY);
}
element.addEventListener('click', clickHandler);
// here we won't have pageX nor pageY properties
function keydownHandler(evt){
do_something_else();
// create a fake object, which doesn't need to be an Event
var fake_evt = {pageX: someValue, pageY: someValue};
do_something_with(fake_evt.pageX, fake_evt.pageY);
}
element.addEventListener('keydown', keydownHandler);
Note : you are mixing jQuery.on and element.addEventListener, so you might need to pass the originalEvent property of the jQuery event object.
I have a "ship" that I have drawn on my canvas and I want to be able to drag it left and right on the bottom of the canvas when it is clicked. I have done some research but I'm having no luck.
I'm assuming I use onMouseDown to do this so I have this when the page loads...
canGame.onMouseDown = canCanvas.onMouseDown(e);
and here is the code that creates my ship
gShip = spriteNew("MediumSpringGreen", 230, 950, 150, 20);
From there I am not really sure what to do. I have a gameUpdate function which moves the invaders down the screen. A gameInit function that will draw the invaders and ship on the screen. Also I have a gameDraw function which draws everything on the screen. I have a few others which are not really important for my issue.
Here is the jsfiddle with the full source code. For some reason though it will run on my browser when I run the HTM file but not in jsfiddle.
http://jsfiddle.net/f66bk/2/
Like I said in comment, the best way of doing this kind of behaviour is by using the events mouseup, mousedown and mousemove.
I defined a var named isMouseDown to know that the ship is dragged that I set to true on mousedown and false on mouseup:
canGame.onMouseDown = function() { isMouseDown = true }
canGame.onMouseUp = function() { isMouseDown = false }
You will also need the event onmousemove to get your mouse position to redraw your ship properly:
canGame.onMouseUp = function(event) { moveShip(event, this) }
In the function moveShip, you can get your mouse position and set the position of your ship properly:
function moveShip(evt, obj) {
if(!isMouseDown)
return;
var target = evt.target || evt.srcElement,
rect = target.getBoundingClientRect(),
offsetX = evt.clientX - rect.left;
gShip.Rect.X = offsetX - gShip.Rect.Width/2;
}
Here is your jsfiddle :)