Limit resize in Fabric.js - html5-canvas

I'm trying to limit max object resize in Fabric.js. I'm using observe("object:scaling") with this code:
canvasLI.observe("object:scaling", function(e) {
var shape = e.target;
var scaleX = shape.scaleX;
var scaleY = shape.scaleY;
var posX = shape.left;
var posY = shape.top;
if(!isNaN(scaleX) && scaleX > 2)
{
shape.set({
scaleX: 2,
});
}
if(!isNaN(scaleY) && scaleY > 2)
{
shape.set({
scaleY: 2,
});
}
shape.set({
left: posX,
top: posY,
});
console.log("X:" + ( shape.scaleX) + " Y:" + (shape.scaleY));
});
It works fine, and limit max size in scale, but when one object has scale limit and try to resize again, it moves following the mouse cursor on top and bottom-left controls (not in bottom-right control).
Also, I tried to capture left and top values in the object to move it to these values, but doesn't work.
Thanks.
PS: Also tried to change shape.width and shape.height instead of scaleX and scaleY and the problem is the same.
EDIT:
I have been could stop the movement of the object on scaling adding lockScalingX:true and lockScalingY:true to the code on max-scale, but now I have another problem. How I can disable this lockScaling putting this value to false when I scale DOWN? How I can get the event of scale DOWN?

You can use the callback onBeforeScaleRotate
canvas.onBeforeScaleRotate = function lock(object) {
object.set({ lockScalingX:false, lockScalingY: false });
};

The following will stop the object to resize over the canvas size and resize in the reverse side.
canvas.on('object:scaling', function(event){
var el = event.target;
if (el && (el.height*el.scaleX) > 10 && (el.left + (el.width*el.scaleX)) < canvas.width && (el.top + (el.height*el.scaleY)) < canvas.height){
previous_scaleY=el.scaleY;
previous_scaleX=el.scaleX;
previous_left=el.left;
previous_top=el.top;
}
if(el && (el.height*el.scaleX) <10) {
el.left=previous_left;
el.top=previous_top;
el.scaleX=previous_scaleX;
el.scaleY=previous_scaleY;
el.lockScalingX=true;
el.lockScalingY= true;
canvas.renderAll();
}
if (el && (el.left + (el.width*el.scaleX)) > canvas.width || (el.top + (el.height*el.scaleY)) > canvas.height){
console.log('mOVINGGGGGGGGGGGGG');
el.left=previous_left;
el.top=previous_top;
console.log('Scale X:'+el.scaleX);
console.log('Scale Y:'+el.scaleY);
console.log('width:'+el.width);
console.log('Height:'+el.height);
console.log('Previous Y: '+previous_scaleY); // PREVIOUS SCALE X & Y WILL COME FROM MOUSE DOWN EVENT
el.scaleX=previous_scaleX;
el.scaleY=previous_scaleY;
canvas.renderAll();
}
});
canvas.observe('mouse:up', function (e) {
var activeObject = e.target;
activeObject.lockScalingX=false; // this will connect with object scalling event
activeObject.lockScalingY= false;
});

Related

d3js zooming in one direction, panning in both directions

I'm trying to build a d3js chart that zooms only on the X-axis but allows panning on both axes. The example below has the effect I desire:
https://jsfiddle.net/xpr364uo/
However, I'm having trouble translating this into my own code. For one, I'm rendering to canvas so I don't have the ability to set the "transform" attribute on some element. Also my zooming uses rescaleX/rescaleY on copies of the scales, as is the "new way" to do zooming via d3-zoom, from what I understand:
const zoomBehavior = zoom().on('zoom', () => {
const xDomain = event.transform.rescaleX(x2).domain();
const yDomain = event.transform.rescaleY(y2).domain();
xScale.domain(xDomain);
yScale.domain(yDomain);
render();
});
This works to zoom/pan on both axes. How can I modify it to get the same affect as in the fiddle? What am I supposed to do with deltaPanY (from the fiddle), in my code?
You could keep track of a second zoom transform (I'll call this yTransform) and use this to rescale the y axis. As you want the x to zoom normally, you can still use d3.event.transform.rescaleX() to rescale on the X axis, while the yTransform can be used to rescale on the Y axis.
When panning, the y translate value of yTransform should be updated with the current zoom state. Conversely, when zooming, yTransform should be used to override the change in the zoom state's y translate.
Perhaps something like:
var yTransform = d3.zoomIdentity; // initial state for the y transform
var zoom = d3.zoom()
.on("zoom", function() {
var t = d3.event.transform; // zoom state
x2 = t.rescaleX(x); // rescale x as normal (t.y is irrelevant)
// for a pan event, update the y translate
if (d3.event.sourceEvent.type != "wheel") yTransform.y = t.y;
// for a scroll, use the current y translate
else t.y = yTransform.y;
y2 = yTransform.rescaleY(y); // rescale y.
render();
})
The k and x values for yTranslate don't matter: the scale is always 1 as we aren't zooming in, and the x translate is irrelevant to rescale on the y axis. The above doesn't account for double click events, but I'll add that below.
var dots = d3.range(100)
.map(function() {
return {x: Math.random(), y: Math.random()}
})
var x = d3.scaleLinear().range([0,500])
var x2 = x.copy();
var y = d3.scaleLinear().range([0,300])
var y2 = y.copy();
var canvas = d3.select("canvas")
var context = canvas.node().getContext("2d");
// Just for reference:
var axis = d3.axisRight(y);
var g = d3.select("svg").append("g");
g.call(d3.axisRight(y2))
render();
var yTransform = d3.zoomIdentity;
var zoom = d3.zoom()
.on("zoom", function() {
var t = d3.event.transform;
x2 = t.rescaleX(x);
// For dbl clicks, d3.event.sourceEvent is null.
if (d3.event.sourceEvent && d3.event.sourceEvent.type != "wheel") yTransform.y = t.y;
else t.y = yTransform.y;
y2 = yTransform.rescaleY(y);
render();
})
canvas.call(zoom);
function render() {
context.clearRect(0,0,500,300);
dots.forEach(function(d) {
context.beginPath();
context.arc(x2(d.x), y2(d.y), 5, 0, 2 * Math.PI);
context.stroke();
})
g.call(d3.axisRight(y2));
}
canvas, svg {
position: absolute;
top: 0;
left: 0;
}
svg {
pointer-events:none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<canvas width="500" height="300"></canvas>
<svg width="500" height="300"></svg>
As I'm modifying properties directly - which is not the most ideal.
Alternatively, we could track a translate offset on the y (the difference between a y translate with dbl clicks/wheel events and without those events). Both y offset and y translate could be used to create an appropriate zoom transform:
var yOffset = 0;
var lastY = 0;
var zoom = d3.zoom()
.on("zoom", function() {
var t = d3.event.transform;
x2 = t.rescaleX(x);
// For dbl clicks, d3.event.sourceEvent is null.
if (d3.event.sourceEvent && d3.event.sourceEvent.type != "wheel") {
lastY = t.y - yOffset;
y2 = d3.zoomIdentity.translate(0,t.y-yOffset).rescaleY(y);
}
else {
yOffset = t.y - lastY; // ignore change in y for dbl click and wheel events
}
render();
})
var dots = d3.range(100)
.map(function() {
return {x: Math.random(), y: Math.random()}
})
var x = d3.scaleLinear().range([0,500])
var x2 = x.copy();
var y = d3.scaleLinear().range([0,300])
var y2 = y.copy();
var canvas = d3.select("canvas")
var context = canvas.node().getContext("2d");
// Just for reference:
var axis = d3.axisRight(y);
var g = d3.select("svg").append("g");
g.call(d3.axisRight(y2))
render();
var yOffset = 0;
var lastY = 0;
var zoom = d3.zoom()
.on("zoom", function() {
var t = d3.event.transform;
x2 = t.rescaleX(x);
// For dbl clicks, d3.event.sourceEvent is null.
if (d3.event.sourceEvent && d3.event.sourceEvent.type != "wheel") {
lastY = t.y - yOffset;
y2 = d3.zoomIdentity.translate(0,t.y-yOffset).rescaleY(y);
}
else {
yOffset = t.y - lastY; // ignore change in y for dbl click and wheel events
}
render();
})
canvas.call(zoom);
function render() {
context.clearRect(0,0,500,300);
dots.forEach(function(d) {
context.beginPath();
context.arc(x2(d.x), y2(d.y), 5, 0, 2 * Math.PI);
context.stroke();
})
g.call(d3.axisRight(y2));
}
canvas, svg {
position: absolute;
top: 0;
left: 0;
}
svg {
pointer-events:none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<canvas width="500" height="300"></canvas>
<svg width="500" height="300"></svg>

Rotate an object like OrbitControls but only the object itself

We love three.js! And here is a page we built using it a few years ago.
https://www.jgprolock.com
We are in the process of revising the animations on this site.
Once the page loads, the user has the ability to drag and rotate the object. But it is really a trick. We are using orbit controls to rotate the camera around our scene, and thus our main object which is centered in the scene (positions x,y,z all equal to 0). If we did not place the object in the center, it starts to look uneven in its rotation as the camera now is rotating around a center that the object doesn't have.
In order to make it look like the object is on the left side, we ended up moving the canvas to the left and then we bring it back to the right or left as the animation continues after scrolling.
So, my question is .. does anyone have an example how to achieve this functionality just by rotating the actual object itself, instead of rotating the camera around the entire scene using the orbit controls plugin?
Or is there away to modify the orbit controls to rotate around an object and not the entire scene?
I've been searching for this for a while but right after asking this question I came across this link, which actually has an example of what we are trying to do.
https://jsfiddle.net/n6u6asza/1205/
The key to making this work as copied from the link: (although I am not 100% sure what this all means)
/* */
var isDragging = false;
var previousMousePosition = {
x: 0,
y: 0
};
$(renderer.domElement).on('mousedown', function(e) {
isDragging = true;
})
.on('mousemove', function(e) {
//console.log(e);
var deltaMove = {
x: e.offsetX-previousMousePosition.x,
y: e.offsetY-previousMousePosition.y
};
if(isDragging) {
var deltaRotationQuaternion = new three.Quaternion()
.setFromEuler(new three.Euler(
toRadians(deltaMove.y * 1),
toRadians(deltaMove.x * 1),
0,
'XYZ'
));
cube.quaternion.multiplyQuaternions(deltaRotationQuaternion, cube.quaternion);
}
previousMousePosition = {
x: e.offsetX,
y: e.offsetY
};
});
/* */
If you want an article on how to achieve this without the use of unnecessary jquery dependencies you can have a look here
This uses the eventListener to find a mousemove event whilst a mousedown event is occurring, and then passes the coordinates to a custom function.
var mouseDown = false,
mouseX = 0,
mouseY = 0;
var canvas = renderer.domElement
canvas.addEventListener('mousemove', function (evt) {
if (!mouseDown) {return}
//console.log('drag')
evt.preventDefault();
var deltaX = evt.clientX - mouseX,
deltaY = evt.clientY - mouseY;
mouseX = evt.clientX;
mouseY = evt.clientY;
// DO SOMETHING HERE WITH X and Y
object.rotation.x += deltaX
}, false);
canvas.addEventListener('mousedown', function (evt) {
evt.preventDefault();
mouseDown = true;
mouseX = evt.clientX;
mouseY = evt.clientY;
}, false);
canvas.addEventListener('mouseup', function (evt) {
evt.preventDefault();
mouseDown = false;
}, false);
}
But not that this will not work if you have OrbitControls or DragControls imported!

So smooth scrolling like on this website

How to implement smooth scrolling like on this website?
https://boredomdoctors.com/banjo-dog/
I've found on website similar solution:
if (window.addEventListener) window.addEventListener('DOMMouseScroll', wheel, false);
window.onmousewheel = document.onmousewheel = wheel;
function wheel(event) {
var delta = 0;
if (event.wheelDelta) delta = event.wheelDelta / 120;
else if (event.detail) delta = -event.detail / 2;
handle(delta);
if (event.preventDefault) event.preventDefault();
event.returnValue = false;
}
function handle(delta) {
var time = 900; // delay time
var distance = 140; // delta point
// Dom where it will apply
$('html, body').stop().animate({
scrollTop: $(window).scrollTop() - (distance * delta)
}, time, 'easeOutExpo' );
}

Kinetic js rotate to mouse with stage click

got a problem with firing the mouse position event. The Object are moving when I drag them but not when I click on stage. What should I do? (of course I am adding a stage and layer and stuff ;) Thanks a lot in advance!
function rotateAlltoMouse(layer3) {
for(var n = 0; n < layer3.getChildren().length; n++) {
var shape = layer3.getChildren()[n];
var stage = shape.getStage();
var mousePos = stage.getMousePosition();
var xd = shape.getPosition().x - mousePos.x;
var yd = shape.getPosition().y - mousePos.y ;
var theta = Math.atan2(yd, xd);
var degree = theta / (Math.PI / 180) - 45;
shape.setRotationDeg(degree);
}
}
$('#container').bind('mousemove touchstart', function() {
rotateAlltoMouse(layer3);
});
Just a guess---could be better than a guess if you supply more code ;)
Try requesting the containing DOM element with kineticJS so that you know you're getting the proper container. KineticJS creates some supporting elements for its own use and your #container might not be getting the stage.
$(stage.getDOM()).on('mousemove', function(){ rotateAlltoMouse(layer3); });

Drag image on canvas

I'm trying to drag an image around a canvas element. Although I've had dragging working, it's not working as I'd quite like.
Basically, the image is always bigger than the canvas element, but the left side of the image within the canvas is not able to further right than the left side of the canvas, likewise the right side is not allowed to go "more right" than the right side of the canvas. Basically, the image is constrained to not show any blank canvas space.
The problem with the dragging is that whenever I start to drag the image "pops" back to act as if 0,0 is from the mouse location, whereas I actually want to move the image from the current position.
document.onmousemove = function(e) {
if(mouseIsDown) {
var mouseCoords = getMouseCoords(e);
offset_x += ((mouseCoords.x - canvas.offsetLeft) - myNewX);
offset_y += ((mouseCoords.y - canvas.offsetTop) - myNewY);
draw(offset_x, offset_y);
// offset_x = ((mouseCoords.x - canvas.offsetLeft) - myNewX);
// offset_y = ((mouseCoords.y - canvas.offsetTop) - myNewY);
// offset_x = (mouseCoords.x - canvas.offsetLeft) - myNewX;
// offset_y = (mouseCoords.y - canvas.offsetTop) - myNewY;
offset_x = prevX;
offset_y = prevY;
}
/*if(mouseIsDown) {
var mouseCoords = getMouseCoords(e);
var tX = (mouseCoords.x - canvas.offsetLeft);
var tY = (mouseCoords.y - canvas.offsetTop);
var deltaX = tX - prevX;
var deltaY = tY - prevY;
x += deltaX;
y += deltaY;
prevX = x;
prevY = y;
draw(x, y);
}*/
};
Is what I have now, where I kind of get a parallelex effect.
You will have to record the current offset of the image as it is moved and use that (in addition to the offset from the top left of the canvas) with each mousedown to determine your initial offset.
var dragging = false,
imageOffset = {
x: 0,
y: 0
},
mousePosition;
function dragImage(coords) {
imageOffset.x += mousePosition.x - coords.x;
imageOffset.y += mousePosition.y - coords.y;
// constrain coordinates to keep the image visible in the canvas
imageOffset.x = Math.min(0, Math.max(imageOffset.x, canvas.width - imageWidth));
imageOffset.y = Math.min(0, Math.max(imageOffset.y, canvas.height - imageHeight));
mousePosition = coords;
}
function drawImage() {
// draw at the position recorded in imageOffset
// don't forget to clear the canvas before drawing
}
function getMouseCoords(e) {
// return the position of the mouse relative to the top left of the canvas
}
canvas.onmousedown = function(e) {
dragging = true;
mousePosition = getMouseCoords(e);
};
document.onmouseup = function() {
dragging = false;
};
document.onmousemove = function(e) {
if(dragging) dragImage(getMouseCoords(e));
};
You should probably treat this as pseudocode as I haven't tested it in any way... ;-)

Resources