I created some objects with fabric js and added different cursors for them if the user hovers with his mouse over the object. i want to add a function which changes the cursor for the whole canvas and all its objects and then changes it back to its default values (so the cursor settings for the objects and the canvas).
is there a way to do this simple? or do i have to set the cursor for every object manually?
I got a small jsFiddle for this: https://jsfiddle.net/bxgox7cr/14/
If you click on the button "change Cursor", the cursor is changed to a crosshair, but when you move your mose over the lines or circles, it is changed to their settings. I want the cursor to stay as a crosshair. After pressing "default Cursor" all the settings should be set back, so the cursor should use the special settings for lines and circles.
the main functionality is in this 2 functions:
$('#button').click(function() {
canvas.defaultCursor = 'crosshair';
});
$('#button2').click(function() {
canvas.defaultCursor = 'default';
});
I might save the default values for every object and then change them back after the click of button2, but this seems to be a difficult solution, so I'm hoping there might be an easier way.
Just add to your code this event:
canvas.on('mouse:over', function(event){
if (event.target != null)
event.target.hoverCursor = canvas.defaultCursor;
}
});
On each object you will reset 'hover' cursor what you set for whole canvas.
Updated:
If you need reset to default you have to keep track of original cursors for each object. You need something like this:
$('#button').click(function() {
changeCursor('crosshair');
});
$('#button2').click(function() {
resetCursor();
});
var cursors = {canvasDefault: canvas.defaultCursor,
canvasDefault: 'default',
object:[
{objectType: 'circle',
hoverCursor: 'move'},
{objectType: 'line',
hoverCursor: 'ns-resize'}
]
};
function changeCursor(cursor){
canvas.forEachObject(function(obj){
for (var i in cursors.object){
if (cursors.object[i].objectType == obj.type){
obj.hoverCursor = cursor;
}
}
canvas.defaultCursor = cursor;
});
}
function resetCursor(){
canvas.forEachObject(function(obj){
for (var i in cursors.object){
if (cursors.object[i].objectType == obj.type){
obj.hoverCursor = cursors.object[i].hoverCursor;
}
}
canvas.defaultCursor = cursors.canvasDefault;
});
}
Related
I am developing a game in JavaScript, which consists in being able to move on the screen and create walls, by holding the left click button, while moving the cursor around on the canvas. On the other hand, by pressing right click, one bullet is created. The problem appears when one bullet is created, by right clicking, which, also, determines the window.onmousedown() function to take place. I do not want to create walls by right clicking on the screen, because the right click is only supposed to create a bullet, not a wall, as the wall should only be created by the left click.
I've tried to create a variable, called wallCannotBeCreated, which is set to false, by default. In my mind, the window.oncontextmenu() function should take place, when right click event takes place, thus wallCannotBeCreated becomes true, in order that window.onmousedown() function is becoming "unable" to create any new wall, until window.onmouseup() function takes place, where wallCannotBeCreated becomes, again, false. Unfortunatelly, it doesn't seem to work, as, always, window.onmousedown() function occurs before window.oncontextmenu() function.
var wallCannotBeCreated = false;
window.oncontextmenu = function (e)
{
//alert("right click was pressed");
// a new bullet must be created
bullets[++counterBullets] = new Bullet(player.x + player.r / 2, player.y, e.x, e.y);
wallCannotBeCreated = true;
console.log(wallCannotBeCreated);
}
window.onmousedown = function()
{
console.log(wallCannotBeCreated);
if(wallCannotBeCreated == false)
{
//console.log("mouse is being pressed");
Keys.click = true;
// one wall must be created
walls[++wallNumber] = new Wall();
walls[wallNumber].timerStart();
}
}
window.onmouseup = function()
{
//console.log("mouse was released");
Keys.click = false;
wallCannotBeCreated = false;
}
I'm pretty much new to canvas. What I'm trying to make is that I can write text in canvas using input and can be able to resize it by dragging it's corners. Also I should be able to drag text position within the canvas.
Following is the screen shot of what I want!
Canvas is raster, not vector. By simply drawing and resizing text you would expect it to get blurry or pixelated. And redrawing the whole canvas each time user moves the cursor while resizing will not result in the best performance. Consider using svg instead. In case you do need canvas and don't want to implement all the functions yourself, you can use the paperjs library.
http://paperjs.org/reference/pointtext/
As #hirasawa-yui mentioned, you can use Paper.js to greatly facilitate the implementation of what you want in a canvas.
Here is a simplified sketch showing a possible implementation of dragging/resizing interactions.
// create item
var item = new PointText({
content: 'Custom text content',
point: view.center,
justification: 'center',
fontSize: 30,
selected: true
});
// init variables so they can be shared by event handlers
var resizeVector;
var moving;
// on mouse down...
function onMouseDown(event) {
// ...do a hit test on item bounds with a small tolerance for better UX
var cornerHit = item.hitTest(event.point, {
bounds: true,
tolerance: 5
});
// if a hit is detected on one of the corners...
if (cornerHit && ['top-left', 'top-right', 'bottom-left', 'bottom-right'].indexOf(cornerHit.name) >= 0) {
// ...store current vector from item center to point
resizeVector = event.point - item.bounds.center;
// ...else if hit is detected inside item...
} else if (item.hitTest(event.point, { fill: true })) {
// ...store moving state
moving = true;
}
}
// on mouse drag...
function onMouseDrag(event) {
// ...if a corner was previously hit...
if (resizeVector) {
// ...calculate new vector from item center to point
var newVector = event.point - item.bounds.center;
// scale item so current mouse position is corner position
item.scale(newVector / resizeVector);
// store vector for next event
resizeVector = newVector;
// ...if item fill was previously hit...
} else {
// ...move item
item.position += event.delta;
}
}
// on mouse up...
function onMouseUp(event) {
// ... reset state
resizeVector = null;
moving = null;
}
// draw instructions
new PointText({
content: 'Drag rectangle to move, drag corners to resize.',
point: view.center + [0, -50],
justification: 'center'
});
I try to implement a way to prevent the updating of values with mouse (actually when the three.js animation has started, launched with a click on button).
For the moment, I have the following dat.GUI menu:
Once "start" button is clicked, I would like to prevent user from modifying with mouse the parameters "Rotation x" and "Rotation y".
Here is the concerned part of code for this menu:
// Create GUI
var gui = new dat.GUI({
autoplace: false,
width: 350,
height: 9 * 32 - 1
});
var params = {
GreatCircle : '',
Rotationx : torusRotationInitX,
Rotationy : torusRotationInitY,
StartingVector : '',
ComponentVectorTheta : 15.0,
ComponentVectorPhi : 15.0,
CovariantDerivativeVector : '',
ComponentCovariantDerivativeTheta : 15.0,
ComponentCovariantDerivativePhi : 15.0
};
// Set parameters for GUI
gui.add(params, 'GreatCircle').name('Great Circle ');
controllerRotationx = gui.add(params, 'Rotationx', 0, 2*Math.PI, 0.001).name('Rotation x ');
controllerRotationy = gui.add(params, 'Rotationy', 0, 2*Math.PI, 0.001).name('Rotation y ');
...
When I click on reset button, I call the following function:
// Reset Button
resetButton.onclick = function ResetParameters() {
...
// Reinitialize parameters into gui
params.Rotationx = torusRotationInitX;
params.Rotationy = torusRotationInitY;
for (var i in gui.__controllers) {
gui.__controllers[i].updateDisplay();
}
render();
}
I don't know if there is an option for controller to lock these sliders which usually change their values. Is it possible?
Update 1
Maybe I could wrapper the dat.GUI menu into a div and make this div not clickable, is it a solution?
Update 2
I tried to apply the method used on Method for disabling a button in dat.gui?
Following this solution, I have added the extension into dat.gui, just after:
dat.controllers.FunctionController = (function (Controller, dom, common) {
...
});
The following added code snippet is:
function blockEvent(event)
{
event.stopPropagation();
}
Object.defineProperty(dat.controllers.FunctionController.prototype, "disabled", {
get: function()
{
return this.domElement.hasAttribute("disabled");
},
set: function(value)
{
if (value)
{
this.domElement.setAttribute("disabled", "disabled");
this.domElement.addEventListener("click", blockEvent, true);
}
else
{
this.domElement.removeAttribute("disabled");
this.domElement.removeEventListener("click", blockEvent, true);
}
},
enumerable: true
});
Is extension code well located into dat.GUI source?
Then, I set the property "disabled" into my code to prevent user from sliding "controllerRotationx" with mouse (once start button is pressed):
if (animation)
controllerRotationx.__li.disabled = true;
Unfortunately, my method doesn't work : when animation is started, I can still move the slider contained into "controllerRotationx".
I saw that above link (Method for disabling a button in dat.gui?), this was about a button and not for a slider, does it change anything for my case?
I didn't find an explicit controller for the slider.
I would do this. The slider is not a form element, there's nothing to disable in the traditional w3c sense. Luckily we can use pointer-events and disable it properly as if it were a form element using just public dat.gui properties.
var speeder = menu.add(text, 'speed', -5, 5);
speeder.domElement.style.pointerEvents = "none"
speeder.domElement.style.opacity = .5;
The solution given by #Radio works pretty well. But, with sliders, the slider is a sibling of the text box's DOM element. We need to disable pointer events on the div which contains all the controls (and which is not exposed directly by dat.gui). So,
var speeder = menu.add(text, 'speed', -5, 5);
// disables the text box
speeder.domElement.style.pointerEvents = "none"
// disables all controller elements related to "speeder"
speeder.domElement.parentElement.style.pointerEvents = 'none'
When the Start button is pressed, set:
controllerRotationx.__li.setAttribute( "style", "display: none" );
thanks for tips
on my side i hack the Common controller
so able to chainning.
gui.add(this, '_screenW').disable(true);
Common.extend(controller, {
disable: function disable(v) {
this.domElement.style.pointerEvents = v?"none":"auto";
this.domElement.style.opacity = v?.5:1;
return controller;
},
In this jsfiddle I coded with Javascript/Fabric a polyline with lines that can be resized by moving the intersections (i.e. not stretched proportionally as a whole object). Click several times on the canvas and you'll construct the polyline, press Esc to end. Then you can select the polyline by clicking on any of the lines, and unselect by either clicking on the canvas or on a different object.
This is the issue: selecting/unselecting the polyline is unstable. Sometimes, "mouse:down" returns e.target = undefined even though I click on a line, and the opposite, it returns e.target = the line object when I click outside the line (but somehow close to the line).
What's wrong with this code? I apologize if the code is too long, but couldn't find a way to make it shorter (and could motivate some ideas if someone is interested in achieving the same result)
Relevant javascript:
canvas.observe("mouse:down", function (e) {
if (drawing)
drawPoly(e);
else
seePolyline(e);
});
var seePolyline = function(e) {
deselectAll();
var obj = e.target; // e.target contains undefined when it should
// contain the clicked object, and the opposite
if (typeof obj !== 'undefined') {
var type = obj.get('type');
if (type === 'rect' && (obj.line1 || obj.line2) ||
type === 'line' && obj.rect2 ) {
var rect = searchFirstRect(obj);
selectPolyline(rect);
}
}
canvas.renderAll();
};
UPDATE
I believe one of the problems happen when the line is moved; if you click on the area where the line was before (now empty canvas), the polyline is selected.
UPDATE 2
I'm getting closer; when you change coordinates you need to call setCoords() for example:
if (rect.line1) {
rect.line1.set({ 'x2': rect.left, 'y2': rect.top });
rect.line1.setCoords();
}
Updated jsfiddle
The problem that persist is that when I'm close to the line (not on the line) a click is interpreted as if the click is on the line itself. Any ideas will be appreciated.
The problem you are facing is because the bounding box of the lines are bigger than the line. To actually see what is happening, change selectable property of lines to true.
line = new fabric.Line(coords, {
fill: 'black',
stroke: 'black',
strokeWidth: 3,
selectable: true
});
Now when you hover over the lines, wherever you see cursor turning into a hand, those are the places clicking which your polyline will get selected.
A simple solution to this is a property Fabric.js provides called perPixelTargetFind. Just set it to true while creating the lines and they will get selected only if you click on the lines and not the bounding box.
line = new fabric.Line(coords, {
fill: 'black',
stroke: 'black',
strokeWidth: 3,
selectable: false
});
line.hasControls = line.hasBorders = false;
line.perPixelTargetFind = true;
canvas.add(line);
You can find the updated fiddle here: http://jsfiddle.net/w86vcyez/5/ and documentation here: http://fabricjs.com/docs/fabric.Object.html#perPixelTargetFind
I have a few images on an HTML5 canvas, and I want to call a function that will update the text displayed in an HTML <p></p> tag whenever the cursor hovers over one of these images.
The images are being drawn to the canvas using the following function:
function drawBox(imageObj, img_x, img_y, img_width, img_height) {
console.log("function drawBox is drawing the description boxes");
var canvasImage = new Kinetic.Image({
image: imageObj,
width: img_width,
height: img_height,
// puts the image in the middle of the canvas
x: img_x,
y: img_y
});
// add cursor styling
imagesLayer.add(canvasImage);
}
I've tried adding an on mouseover function to each of the images by adding some code to call the function I have that updates the contents of the paragraph to this function, so my drawBox function now looks like this:
function drawBox(imageObj, img_x, img_y, img_width, img_height) {
console.log("function drawBox is drawing the description boxes");
var canvasImage = new Kinetic.Image({
image: imageObj,
width: img_width,
height: img_height,
// puts the image in the middle of the canvas
x: img_x,
y: img_y
});
// add cursor styling
canvasImage.on("mouseover", function(){
//displayAssetDescriptionTip();
console.log("displayAssetDescriptionTip() is being called on mouseover event");
});
imagesLayer.add(canvasImage);
}
When I view my page with this function as it is above, and hover the cursor over one of the images that the function has been added to, the log that I've added to my mouseover function is being displayed in the console- so I know that the mouseover is working correctly.
However, the problem I'm having is with the function that I want to call from within the mouseover event. You'll see above, that I've commented out a call to the function displayAssetDescriptionTip() in my mouseover event. This is the function that I want to be called in the event of the cursor hovering over these images, however, it seems that it always changes the text in the paragraph to the text belonging to the first description box... i.e. if the cursor hovers over any of the images that I've added the mouseover event to, then the text is changed to the text belonging to the first image, rather than the image that the cursor hovered over.
The function is:
function displayAssetDescriptionTip(){
if(canvasImage.id = "assetBox")
document.getElementById('tipsParagraph').innerHTML = tips[34];
else if(canvasImage.id = "liabilitiesBox")
document.getElementById('tipsParagraph').innerHTML = tips[35];
else if(canvasImage.id = "incomeBox")
document.getElementById('tipsParagraph').innerHTML = tips[36];
else if(canvasImage.id = "expenditureBox")
document.getElementById('tipsParagraph').innerHTML = tips[37];
else return;
console.log("displayAssetDescriptionTip being called");
}
Anyone have any idea why it always changes the text to the text for the first image, and not to the text corresponding to any of the other images? I've set it to change the text to a different element of an array depending on which image the cursor is hovering over, so it should display a different element from that array for each image...
in your function displayAssetDescriptionTip you are using the = assignment operator instead of the == comparison operator hence your first if condition is always matched.
The fonction corrected :
function displayAssetDescriptionTip(canvasImage){
if(canvasImage.id == "assetBox")
document.getElementById('tipsParagraph').innerHTML = tips[34];
else if(canvasImage.id == "liabilitiesBox")
document.getElementById('tipsParagraph').innerHTML = tips[35];
else if(canvasImage.id == "incomeBox")
document.getElementById('tipsParagraph').innerHTML = tips[36];
else if(canvasImage.id == "expenditureBox")
document.getElementById('tipsParagraph').innerHTML = tips[37];
else return;
console.log("displayAssetDescriptionTip being called");
}
Don't forget to pass canvasImage as argument to your event handler
canvasImage.on("mouseover", function(){
displayAssetDescriptionTip(canvasImage);
});