context.clearRect() doesn't work as expected - javascript

So I have a maze here and the problem arises when user changes rowNum/colNum in the maze.
When user changes either of them, it calls
ctx.clearRect(0, 0, canvas.width, canvas.height).
This clears up the whole canvas but when user again starts moving the player, the player from previously cleared up canvas appears up in the this canvas. see here.
clearRect() clears up the canvas but I don't know why the player is still left there to interact with.
codesandbox link
Here's a shortened description of what my code does to draw the player-
main.js
let customGrid = document.querySelector("#rows,#columns");
customGrid.forEach(elem => elem.addEventListener("change", e => {
// detect changed element
if (e.target.id === "row")
customGrid[0].value = parseInt(e.target.value);
else
customGrid[1].value = parseInt(e.target.value);
ctx.clearRect(0, 0, canvas.width, canvas.width);
// setting myMaze to new instance of Maze
myMaze = new Maze(ctx, 600, customGrid[0].value, customGrid[1].value);
myMaze.setup();
myMaze.drawMap();
})
);
Next, myMaze.drawMap() contains-
//set player
this.player = new Player(this.ctx, this.goal, this.cellWidth, this.cellHeight);
this.player.setPlayer(this);
From here setPlayer calls-
setPlayer(myMaze) {
....
this.drawPlayer();
this.listenMoves(myMaze);
}
listenMoves(myMaze) {
window.addEventListener("keydown", function handler(e) {
myMaze.player.move(e.keyCode, myMaze);
let reachedCol = myMaze.player.colNum === myMaze.goal.colNum ? true : false;
let reachedRow = myMaze.player.rowNum === myMaze.goal.rowNum ? true : false;
if (reachedRow && reachedCol) {
alert("reached!");
window.removeEventListener("keydown", handler);
}
});
}
move(input, myMaze) {
let current = myMaze.grid[this.rowNum][this.colNum];
let walls = current.walls;
switch(input) {
case 37:
if(!walls.leftWall) {
this.colNum -= 1;
} break;
case 38:
if(!walls.topWall) {
this.rowNum -= 1;
} break;
case 39:
if(!walls.rightWall) {
this.colNum += 1;
} break;
case 40:
if(!walls.bottomWall) {
this.rowNum += 1;
}
}
this.ctx.clearRect(current.xCord, current.yCord, current.width, current.height);
current.drawCell();
this.drawPlayer();
}

I would move the event listener up to main.js. Since Maze has the reference to Player, it should be possible. I am assuming you have a global variable of Maze (myMaze).
let myMaze;
let reched = false;
window.addEventListener("keydown", function handler(e) {
if (!myMaze || reched) {
return;
}
myMaze.player.move(e.keyCode, myMaze);
myMaze.player.handleMove();
let reachedCol = myMaze.player.colNum === myMaze.goal.colNum ? true : false;
let reachedRow = myMaze.player.rowNum === myMaze.goal.rowNum ? true : false;
if (reachedRow && reachedCol) {
alert("reached!");
reched = true;
}
});
If you want to keep the event handler as Player's method, you could do something like below. And call unListenMoves() when the grid size changes.
class Player {
constructor(ctx, goal, cellWidth, cellHeight, myMaze) {
// keep the Maze instance as a Player's prop for the later use
this.myMaze = myMaze;
// we need to bind this here, not in listenMoves
this.handleMove = this.handleMove.bind(this);
}
listenMoves() {
window.addEventListener("keydown", this.handleMove);
}
unListenMoves() {
window.removeEventListener("keydown", this.handleMove);
}
handleMove(e) {
const myMaze = this.myMaze;
myMaze.player.move(e.keyCode, myMaze);
let reachedCol = myMaze.player.colNum === myMaze.goal.colNum ? true : false;
let reachedRow = myMaze.player.rowNum === myMaze.goal.rowNum ? true : false;
if (reachedRow && reachedCol) {
alert("reached!");
window.removeEventListener("keydown", this.handleMove);
}
}
}

Related

P5 JS - Saving an edited soundfile

let play_button = document.querySelector('#play_button');
let reverb_button = document.querySelector('#reverb_button');
let save_button = document.querySelector("#save_button");
let song, reverb, speed_slider, recorder;
let reverb_on = false;
function preload() {
song = loadSound('music/Mood.mp3');
}
function setup() {
speed_slider = createSlider(0.6, 1.5, 1, 0.1); // min, max, start, step
reverb = new p5.Reverb();
reverb.process(song, 10, 10); // 10 seconds duration, 10% decay
}
function draw() {
let val = 0;
if (reverb_on) val = 1;
reverb.drywet(val); // 1 is all reverb
song.rate(speed_slider.value());
}
function togglePlay() {
if (!song.isPlaying()) {
song.play();
play_button.innerText = "Stop"
} else {
song.pause();
play_button.innerText = "Play"
}
}
function toggleReverb() {
if (!reverb_on) {
reverb_button.innerText = 'off';
reverb_on = true;
} else {
reverb_button.innerText = 'on';
reverb_on = false
}
}
function saveSong() {
saveSound(song, 'mood')
}
Above is my code, trying to give the user an option to save the soundfile after they added different effects to it, such as reverb and speed control.
No matter what method I tried that was on the p5 documentation, including:
saveSound() -> https://p5js.org/reference/#/p5/saveSound
save() -> https://p5js.org/reference/#/p5.SoundFile/save
p5.SoundRecorder passing in the sound file itself.
None of them work and only save the original sound; doesn't save any of the 'changes' you make.

How can I cancel setInterval running with global variable?

I am trying to clear/stop setInterval from running, and have made changes to my approach based on other questions/answers I've found, but still can't get setInterval to stop running.
What I have here, is a Mutation Observer which detects a class change, and when that occurs, if two conditions are true (the presence of other classes), then some cursor tracking executes. All this works great.
When one of the two classes, .run-preview, is removed I then want to stop setInterval from running, as seen in the else if. I also have a commented line there where I was verifying that the else if is working, and it is.
I have a global variable intervalCursor = setInterval(move,1000/60);, and then am using that variable to later clearInterval(intervalCursor);.
const runPreview = document.querySelector('.projects');
new MutationObserver((mutations) => {
if (mutations[0].attributeName === 'class') {
console.log('Project list class changed');
if ($('html').hasClass('no-touchevents') && ($('.projects').hasClass('run-preview'))) {
var mouseX=window.innerWidth/2,
mouseY=window.innerHeight/2;
var intervalCursor = setInterval(move,1000/60);
var projectPreview = {
el:$('.image-container'),
x:window.innerWidth/2,
y:window.innerHeight/2,
w:300,
h:300,
update:function() {
l = this.x-this.w/2;
t = this.y-this.h/2;
this.el.css({
'transform':
'translate3d('+l+'px,'+t+'px, 0)' });
//console.log("transform");
}
}
$(window).mousemove (function(e){
mouseX = e.clientX;
mouseY = e.clientY;
//console.log("mousemove");
})
function move(){
projectPreview.x = lerp (projectPreview.x, mouseX, 0.1);
projectPreview.y = lerp (projectPreview.y, mouseY, 0.1);
projectPreview.update()
console.log("move");
}
function lerp (start, end, amt){
return (1-amt)*start+amt*end
}
} else if (!$('.projects').hasClass('run-preview')) {
clearInterval(intervalCursor);
return;
//$('#content-container').addClass('test');
}
}
})
.observe(runPreview, { attributes: true });
Am I doing something wrong?
Thanks to Ouroborus, I was able to solve this by creating the variable separately from the MutationObserver, and then using it to run setInterval(move,1000/60); later. Thus allowing setInterval to be cleared/stopped when I need to.
var intervalCursor
const runPreview = document.querySelector('.projects');
new MutationObserver((mutations) => {
if (mutations[0].attributeName === 'class') {
console.log('Project list class changed');
if ($('html').hasClass('no-touchevents') && ($('.projects').hasClass('run-preview'))) {
var mouseX=window.innerWidth/2,
mouseY=window.innerHeight/2;
var projectPreview = {
el:$('.image-container'),
x:window.innerWidth/2,
y:window.innerHeight/2,
w:300,
h:300,
update:function() {
l = this.x-this.w/2;
t = this.y-this.h/2;
this.el.css({
'transform':
'translate3d('+l+'px,'+t+'px, 0)' });
//console.log("transform");
}
}
$(window).mousemove (function(e){
mouseX = e.clientX;
mouseY = e.clientY;
//console.log("mousemove");
})
intervalCursor = setInterval(move,1000/60);
function move(){
projectPreview.x = lerp (projectPreview.x, mouseX, 0.1);
projectPreview.y = lerp (projectPreview.y, mouseY, 0.1);
projectPreview.update()
console.log("move");
}
function lerp (start, end, amt){
return (1-amt)*start+amt*end
}
} else if (!$('.projects').hasClass('run-preview')) {
clearInterval(intervalCursor);
//$('#content-container').addClass('test');
}
}
})
.observe(runPreview, { attributes: true });

Make onkeydown events occur only once for specific keys

I am creating a simple game with functions to move a player forward,turn left,turn right, or turn around. I want the key presses to fire the specific corresponding function only once. I found this helpful code that fires the event only once, but I cannot seem to figure out how to specify certain key presses within it.
var shouldHandleKeyDown = true;
document.onkeydown = function(){
if (!shouldHandleKeyDown) return;
shouldHandleKeyDown = false;
// HANDLE KEY DOWN HERE
}
document.onkeyup = function(){
shouldHandleKeyDown = true;
}
})();
What I am trying to make happen is:
User presses up?
Move forward function occurs ONCE (even if the up key is held)
User presses left? Turn left function occurs once etc....
Thank you all for any help with this.
EDIT FOR CLARIFICATION:
I am trying to build a first-person dungeon crawler in the style of old titles like Wizardry for the NES. Basically I want each press of the forward button to move the player forward one frame. If they press left then the player turns left one frame, etc. If you are familiar with the games you will get what I mean.
I would ignore onkeydown events and just use onkeyup. That way the event is only fired once the user lifts their finger, ensuring just one movement per key press.
To determine which key was pressed inside your event handler, pass the event to your function. Here is a link to the values of the keys, so you could do something like:
document.onkeyup = function(event) {
var keycode = event.keyCode;
if (keycode === 38) {
moveUp();
}
}
There is no single good way to tackle this problem, it mostly depends on what you'll do with these key inputs.
For instance, if it is to control an always updating animation, then simple semaphores in a dictionary with {left: bool, right: bool, up: bool, down: bool} sets to true in keydown and to false in keyup, and that gets checked at every frame is enough.
const directions = {
up: false,
right: false,
down: false,
left: false
};
onkeydown = e => {
const key = e.key.replace('Arrow', '').toLowerCase();
if(key in directions) {
e.preventDefault();
directions[key] = true;
}
};
onkeyup = e => {
const key = e.key.replace('Arrow', '').toLowerCase();
if(key in directions) {
e.preventDefault();
directions[key] = false;
}
};
const w = canvas.width = innerWidth;
const h = canvas.height = innerHeight;
const ctx = canvas.getContext('2d');
const character = {
x: w/2,
y: h/2,
update: function() {
const speed = 2;
// this is where we will check our keyboard's status
// we do it every frame
let dir_x = 0,
dir_y = 0;
dir_x += directions.left ? -1 : 0;
dir_x += directions.right ? 1 : 0;
dir_y += directions.up ? -1 : 0;
dir_y += directions.down ? 1 : 0;
this.x += dir_x * speed;
this.y += dir_y * speed;
},
draw: function() {
ctx.fillRect(this.x - 20, this.y-20, 40, 40);
}
};
function anim() {
character.update();
ctx.clearRect(0,0,w, h);
character.draw();
requestAnimationFrame(anim);
}
anim();
:root,body{margin:0px}
<canvas id="canvas"></canvas>
However if you are not keeping a continuous animation loop and really need this event to fire at first action, then in the keydown handler you may need to add a logic
...
if(key in directions) {
e.preventDefault();
const changed = directions[key] !== true;
directions[key] = true;
if(changed) {
triggerChange(key);
}
}
Where triggerChange(key) would be a function ready to trigger your solo action.
const directions = {
up: false,
right: false,
down: false,
left: false
};
function triggerChange(dir) {
character.update();
}
onkeydown = e => {
const key = e.key.replace('Arrow', '').toLowerCase();
if (key in directions) {
e.preventDefault();
const changed = directions[key] !== true;
directions[key] = true;
if (changed) {
triggerChange(key);
}
}
};
onkeyup = e => {
const key = e.key.replace('Arrow', '').toLowerCase();
if (key in directions) {
e.preventDefault();
directions[key] = false;
}
};
const w = canvas.width = innerWidth;
const h = canvas.height = innerHeight;
const ctx = canvas.getContext('2d');
const character = {
x: w / 2,
y: h / 2,
update: function() {
const speed = 5;
// this is where we will check our keyboard's status
// we do it every frame
let dir_x = 0,
dir_y = 0;
dir_x += directions.left ? -1 : 0;
dir_x += directions.right ? 1 : 0;
dir_y += directions.up ? -1 : 0;
dir_y += directions.down ? 1 : 0;
this.x += dir_x * speed;
this.y += dir_y * speed;
},
draw: function() {
ctx.fillRect(this.x - 20, this.y - 20, 40, 40);
}
};
function anim() {
// it is now keydown that is responsible for character.update()
ctx.clearRect(0, 0, w, h);
character.draw();
requestAnimationFrame(anim);
}
anim();
:root,body{margin:0}
<canvas id="canvas"></canvas>
Change function() to function(e) and pressed key is in e.key
(after run snippet click on white rectangle below run button and press keys)
var shouldHandleKeyDown = true;
document.onkeydown = function(e) {
if (!shouldHandleKeyDown) return;
shouldHandleKeyDown = false;
console.log(e.key);
// HANDLE KEY DOWN HERE
}
document.onkeyup = function(){
shouldHandleKeyDown = true;
}

possible to alter my tooltip plugin to accept "click" option without too much trouble?

I have a plugin I've written for the purpose of showing/hiding tooltips. It works to the point that I'm happy enough to use it in production work, in the case when all I need is for it to show/hide the tooltip on hover. However I'd like to now alter it to show/hide on click as well.
I can get it to work up to a point but depending on what I try it either doesn't do one of the following: when I click from one tooltip activating element to another the previous tooltip doesn't hide - or - when I click from one element to another the previous tooltip closes but the next tooltip doesn't immediately open as well.
I've tried e.target !== e.currentTarget within an if statement and a number of other things that didn't quite work. My suspicion is that the plugin as it stands is not going to be easily extensible without more than a few simple conditionals, but if anyone can have a look and let me know if possible and if so hopefully offer me some direction or suggestions on how I might go about amending it it would be much appreciated.
If anyone is wondering why I am trying to reinvent the wheel (when there are so many tooltip plugins out there): it's mainly a learning exercise. I'd love to get better at writing reusable code. I'd also like a viable tooltip plugin that suits my needs in regards to being something very lightweight and framework independent, which in my searching I wasn't able to find.
Below is the code as it stands, in one of the two before mentioned states:
(window => {
let template = document.createElement('div'),
pos = {x: 0, y: 0},
targetPos,
tip,
tipWidth,
tipHeight,
innerOffsetX,
delay;
template.inner = document.createElement('div');
template.inner.setAttribute('class', 'tooltip-inner');
template.appendChild(template.inner);
/**
* #param {string|HTMLElement} container Either a selector string or the element.
* #param {Object=} config Optional argument for overriding the default configuration.
*/
class Tooltip {
constructor(container, config) {
this.offsetX = 0;
this.offsetY = 0;
this.position = 'top';
this.margin = 6;
this.offsetBubble = 0;
this.delayShow = this.delayHide = 0;
this.clickToShow = false;
this.tooltipInClass = 'tooltip-in';
this.tooltip = null;
this.toggle = true;
if (typeof container === 'string') {
this.container = document.querySelector(container);
} else {
this.container = container;
}
if (config) {
for (let p in config) {
if (typeof config[p] === 'object') {
this.delayShow = config.delay.show;
this.delayHide = config.delay.hide;
} else {
this[p] = config[p];
}
}
}
template.setAttribute('class', 'tooltip ' + this.position);
}
show() {
return e => {
if (e.target.hasAttribute('data-tooltip')) {
if (this.toggle === true) {
targetPos = e.target.getBoundingClientRect();
pos.x = targetPos.left + e.target.offsetWidth / 2 + this.offsetX;
pos.y = targetPos[this.position] + this.offsetY + document.body.scrollTop;
template.inner.innerText = e.target.getAttribute('data-tooltip');
this.tooltip = document.body.appendChild(template.cloneNode(true));
tip = this.tooltip;
tipWidth = tip.clientWidth;
tipHeight = tip.clientHeight;
pos.x -= tipWidth / 2;
pos.y -= this.position === 'bottom' ? -8 : tipHeight;
// Nudge tooltip content into the window area if needed
if (pos.x + tipWidth > tip.offsetParent.clientWidth) {
innerOffsetX = pos.x + tipWidth - tip.offsetParent.clientWidth + 6;
tip.firstChild.setAttribute('style', `left:-${innerOffsetX}px`);
} else if (pos.x < 0) {
innerOffsetX = -pos.x + 6;
tip.firstChild.setAttribute('style', `left:${innerOffsetX}px`);
}
// Reposition tooltip below/above target and flip arrow if needed
if (pos.y < 0 && this.position !== 'bottom') {
pos.y += tipHeight * 2;
tip.classList.remove(this.position);
tip.classList.add('bottom');
} else if (pos.y + tipHeight > tip.offsetParent.scrollHeight && self.position !== 'top') {
tip.classList.remove(this.position);
tip.classList.add('top');
}
tip.setAttribute('style', `left:${Math.floor(pos.x)}px;top:${Math.floor(pos.y)}px`);
if (this.delayShow !== 0) {
// Don't delay showing the tooltip if entering an adjacent item with a "tooltip" data attribute.
if (e.relatedTarget.hasAttribute('data-tooltip')) {
delay = 0;
} else {
delay = this.delayShow;
}
if (typeof this.timeoutid === 'number') {
clearTimeout(this.timeoutid);
delete this.timeoutid;
}
this.timeoutid = setTimeout(function () {
tip.classList.add(this.tooltipInClass);
this.toggle = false;
}, delay);
} else {
tip.classList.add(this.tooltipInClass);
this.toggle = false;
}
} else {
this.hide('hide');
this.toggle = true;
}
}
};
}
hide(e) {
if (e.target && e.target.hasAttribute('data-tooltip') || typeof e === 'string') {
document.body.lastChild.classList.remove(this.tooltipInClass);
document.body.removeChild(document.body.lastChild);
}
}
init() {
if (!this.clickToShow) {
this.container.addEventListener('mouseover', this.show());
this.container.addEventListener('mouseout', this.hide);
} else {
this.container.addEventListener('click', this.show());
}
}
}
window.Tooltip = window.Tooltip || Tooltip;
})(window);
// Usage examples
const elem = document.querySelector('.container'),
tooltip = new Tooltip(elem, {
offsetX: -2,
delay: {show: 0, hide: 0},
clickToShow: true
}).init();
And a jsfiddle: http://jsfiddle.net/damo_s/et9hLnkt/
Again, any help would be much appreciated.
I changed a little your plugin, I added two thing to handle the problem:
I added this if to control if the link you clicked is the same target if some tooltip is already opened:
if (document.getElementsByClassName("tooltip-in")[0] &&
e.target !== document.getElementsByClassName("current-target-tooltip")[0]) {
this.hide('hide');
this.toggle = true;
document.getElementsByClassName("current-target-tooltip")[0].classList.remove("current-target-tooltip");
}
then I just added the add/remove class on the current target in if/else:
if (this.toggle === true) {
e.target.classList.add('current-target-tooltip');
...
} else {
e.target.classList.remove('current-target-tooltip');
this.hide('hide');
this.toggle = true;
}
and here is your updated Fiddle: http://jsfiddle.net/et9hLnkt/18/

Adding Custom Delete (Back,toFront) Button to Controls

I would like to know if there is an easy way to add a delete, bring to front, bring to back
function into the existing fabric.js object controls.
At the moment I can only click on buttons which would delete object, bring to front etc.
I am working on finding a solution to be able to press X (on the upper right corner) on the object and delete the object.
I guess it will have to do with overwriting, wrapping or subclassing the existing control object.
Maybe I have overseen something and there is an easy solution? Please no Div Wrapper.
Fabric.js does not offer any simple mean of adding controls to objects.
If you want to create your own controls, you will have to overwrite the Object class, notably:
override drawControls, to draw your custom button
you will need to store the button's coordinates, so that you can detect when the user clicks them. C.f. _setCornerCoords and _findTargetCorner
incorporate your action somewhere in __onMouseDown
You don't need to take care of 'undrawing' the controls, as the whole canvas is re-rendered when an object is de-selected.
I hope this helps, and good luck :)
I have implemented it via positioning an html element
canvas.on('object:selected',function(e){
jQuery(".deleteBtn").remove();
var btnLeft = e.target.oCoords.mt.x;
var btnTop = e.target.oCoords.mt.y - 25;
var widthadjust=e.target.width/2;
btnLeft=widthadjust+btnLeft-10;
var deleteBtn = '<p" class="deleteBtn" title="Delete" style="position:absolute;top:'+btnTop+'px;left:'+btnLeft+'px;cursor:pointer;" title="Remove object">✕</p>';
jQuery(".canvas-container").append(deleteBtn);
//.canvas-container is the parent div to the canvas positioned relative via CSS
});
canvas.on('mouse:down',function(e){
if(canvas.getActiveObject())
{
}
else
{
jQuery(".deleteBtn").remove();
}
});
canvas.on('object:modified',function(e){
jQuery(".deleteBtn").remove();
var btnLeft = e.target.oCoords.mt.x;
var btnTop = e.target.oCoords.mt.y - 25;
var widthadjust=e.target.width/2;
btnLeft=widthadjust+btnLeft-10;
var deleteBtn = '<p" class="deleteBtn" title="Delete" style="position:absolute;top:'+btnTop+'px;left:'+btnLeft+'px;cursor:pointer;" title="Remove object">✕</p>';
jQuery(".canvas-container").append(deleteBtn);
//.canvas-container is the parent div to the canvas positioned relative via CSS
});
//THE DELETE BUTTON CLICK EVENT
jQuery(document).on('click',".deleteBtn",function(){
if(canvas.getActiveObject())
{
canvas.remove(canvas.getActiveObject());
//this would remove the currently active object on stage,
jQuery(this).remove();
jQuery(".deleteBtn").remove();
}
})
i believe the the solution with the dom elements is not so stable but its ok if it covers your needs,
I also needed to change the default object's corner buttons , to my custom buttons for my web application.
I use ,
'alter size' button, /i dont use it any more for my reasons/
'delete object' button
'edit object' button
'rotate object' button
so you have to change 2 things in order to succeed that:
1. change the private function '_drawControl' from fabric.js file. This is about on line 13367 (on my fabric.js). On that function fabric draws the default cornet buttons of objects and show them on seleced.
We can easily change the png to our custom ones.
below is my changed _drawControl(fabric.js):
_drawControl: function(control, ctx, methodName, left, top, flipiX, flipiY) {
var sizeX = this.cornerSize / this.scaleX,
sizeY = this.cornerSize / this.scaleY;
if (this.isControlVisible(control)) {
isVML || this.transparentCorners || ctx.clearRect(left, top, sizeX, sizeY);
var SelectedIconImage = new Image();
var lx='';
var ly='';
var n='';
switch (control)
{
case 'tl':
if (flipiY) { ly='b'; } else { ly = 't'; }
if (flipiX) { lx='r'; } else { lx = 'l'; }
break;
case 'tr':
if (flipiY) { ly='b'; } else { ly = 't'; }
if (flipiX) { lx='l'; } else { lx = 'r'; }
break;
case 'bl':
if (flipiY) { ly='t'; } else { ly = 'b'; }
if (flipiX) { lx='r'; } else { lx = 'l'; }
break;
case 'br':
if (flipiY) { ly='t'; } else { ly = 'b'; }
if (flipiX) { lx='l'; } else { lx = 'r'; }
break;
default:
ly=control.substr(0, 1);
lx=control.substr(1, 1);
break;
}
control=ly+lx;
switch (control)
{
case 'tl':
//my custom png for the object's top left corner
SelectedIconImage.src = 'assets/img/icons/draw_control/icon_rotate.png';
break;
case 'tr':
if (flipiX && !flipiY) { n='2'; }
if (!flipiX && flipiY) { n='3'; }
if (flipiX && flipiY) { n='4'; }
//my custom png for the object's top right corner
SelectedIconImage.src = 'assets/img/icons/draw_control/icon_delete.png';
break;
case 'mt':
SelectedIconImage.src = //add your png here if you want middle top custom image;
break;
case 'bl':
if (flipiY) { n='2'; }
SelectedIconImage.src = //add your png here if you want bottom left corner custom image;
break;
case 'br':
if (flipiX || flipiY) { n='2'; }
if (flipiX && flipiY) { n=''; }
//my custom png for the object's bottom right corner
SelectedIconImage.src = 'assets/img/icons/draw_control/icon_settings.png';
break;
case 'mb':
SelectedIconImage.src = //middle bottom png here ;
break;
case 'ml':
SelectedIconImage.src = 'assets/img/icons/draw_control/icono_escala_horizontal'+n+'.jpg';
break;
case 'mr':
SelectedIconImage.src = //middle right png here;
break;
default:
ctx[methodName](left, top, sizeX, sizeY);
break;
}
// keep middle buttons size fixed
if (control == 'tl' || control == 'tr' || control == 'bl' || control == 'br'
|| control == 'mt' || control == 'mb' || control == 'ml' || control == 'mr')
{
sizeX = 19;
sizeY = 19;
ctx.drawImage(SelectedIconImage, left, top, sizeX, sizeY);
}
try {
ctx.drawImage(SelectedIconImage, left, top, sizeX, sizeY);
} catch (e) {
if (e.name != "NS_ERROR_NOT_AVAILABLE") {
throw e;
}
}
}
},
As Toon Nelissen mentioned before, i overide the fabric.Canvas.prototype.__onMouseDown function , and control my custom buttons.
fabric.Canvas.prototype.__onMouseDown = function (e) {
// accept only left clicks
var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1;
if (!isLeftClick && !fabric.isTouchSupported) {
return;
}
if (this.isDrawingMode) {
this._onMouseDownInDrawingMode(e);
return;
}
// ignore if some object is being transformed at this moment
if (this._currentTransform) {
return;
}
var target = this.findTarget(e),
pointer = this.getPointer(e, true);
//if user clicked on the top right corner image
if (target && target.__corner === 'tr') {
//my code goes here
}
} else {
// save pointer for check in __onMouseUp event
this._previousPointer = pointer;
var shouldRender = this._shouldRender(target, pointer),
shouldGroup = this._shouldGroup(e, target);
if (this._shouldClearSelection(e, target)) {
this._clearSelection(e, target, pointer);
} else if (shouldGroup) {
this._handleGrouping(e, target);
target = this.getActiveGroup();
}
if (target && target.selectable && !shouldGroup) {
this._beforeTransform(e, target);
this._setupCurrentTransform(e, target);
}
// we must renderAll so that active image is placed on the top canvas
shouldRender && this.renderAll();
this.fire('mouse:down', { target: target, e: e });
target && target.fire('mousedown', { e: e });
}
};
For the rest corners we as well write the appropriate snippet(inside __onMouseDown):
//if user clicked on the bottom right corner image
if (target && target.__corner === 'br') {
//my code here
}else{
//the same as 'tr'
}
//if user clicked on the top left corner image
if (target && target.__corner === 'tl') {
//my code here
}else{
//the same as 'tr'
}
//if user clicked on the bottom left corner image
if (target && target.__corner === 'bl') {
//my code here
}else{
//the same as 'tr'
}
below is a screenshot of my web app's custom images
You can try with html buttons. Look at the example:
http://fabricjs.com/interaction-with-objects-outside-canvas/
Here is the code example:
(function() {
var canvas = this.__canvas = new fabric.Canvas('c');
fabric.Object.prototype.transparentCorners = false;
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
fabric.Canvas.prototype.getAbsoluteCoords = function(object) {
return {
left: object.left + this._offset.left,
top: object.top + this._offset.top
};
}
var btn = document.getElementById('inline-btn'),
btnWidth = 85,
btnHeight = 18;
function positionBtn(obj) {
var absCoords = canvas.getAbsoluteCoords(obj);
btn.style.left = (absCoords.left - btnWidth / 2) + 'px';
btn.style.top = (absCoords.top - btnHeight / 2) + 'px';
}
fabric.Image.fromURL('../lib/pug.jpg', function(img) {
canvas.add(img.set({ left: 250, top: 250, angle: 30 }).scale(0.25));
img.on('moving', function() { positionBtn(img) });
positionBtn(img);
});
})();
You can overwrite the __onMouseDown function for example like this.
the target object contains __corner element target.__corner
Check if this is 'tr' (top right) and delete activeObject
if (target.__corner === 'tr') {
if(canvas.getActiveObject()){
canvas.remove(canvas.getActiveObject());
}
}
Full code:
fabric.Canvas.prototype.__onMouseDown = function (e) {
// accept only left clicks
var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1;
if (!isLeftClick && !fabric.isTouchSupported) {
return;
}
if (this.isDrawingMode) {
this._onMouseDownInDrawingMode(e);
return;
}
// ignore if some object is being transformed at this moment
if (this._currentTransform) {
return;
}
var target = this.findTarget(e),
pointer = this.getPointer(e, true);
if (target && target.__corner === 'tr') {
if(this.getActiveObject()){
this.remove(this.getActiveObject());
}
} else {
// save pointer for check in __onMouseUp event
this._previousPointer = pointer;
var shouldRender = this._shouldRender(target, pointer),
shouldGroup = this._shouldGroup(e, target);
if (this._shouldClearSelection(e, target)) {
this._clearSelection(e, target, pointer);
} else if (shouldGroup) {
this._handleGrouping(e, target);
target = this.getActiveGroup();
}
if (target && target.selectable && !shouldGroup) {
this._beforeTransform(e, target);
this._setupCurrentTransform(e, target);
}
// we must renderAll so that active image is placed on the top canvas
shouldRender && this.renderAll();
this.fire('mouse:down', { target: target, e: e });
target && target.fire('mousedown', { e: e });
}
};

Categories

Resources