object:scaling is not working with bounding object - javascript

I am new in Fabric js. can anyone suggest me for restrict scale object within bounding box ?
my java-script code is below
(function(global) {
var canvas = new fabric.Canvas('maincanvas');
var yourNameObjs = [];
var goodtop =358, goodleft=250, boundingObject;
var boundingObject = new fabric.Rect({
left: 100,
top: 90,
width: 200,
height: 250,
opacity: 4,
selectable:false,
fill: 'red',
});
canvas.add(boundingObject);
addText = function(){
var nametoprint = $("#nametoprint").val();
canvas.remove(yourNameObjs);
yourNameObjs = new fabric.Text(nametoprint, {
left: 150, //Take the block's position
opacity: 9,
top: 150,
fontFamily: 'Delicious_500',
}
);
canvas.add(yourNameObjs);
canvas.on("object:moving", function(){
var obj = yourNameObjs;
var bounds = boundingObject;
obj.setCoords();
if(!obj.isContainedWithinObject(bounds)){
obj.setTop(goodtop);
obj.setLeft(goodleft);
} else {
goodtop = obj.top;
goodleft = obj.left;
}
});
canvas.on("object:scaling", function(){
var obj = yourNameObjs;
var bounds = boundingObject;
obj.setCoords();
if(!obj.isContainedWithinObject(bounds)){
obj.setTop(goodtop);
obj.setLeft(goodleft);
} else {
goodtop = obj.top;
goodleft = obj.left;
}
});
canvas.renderAll();
};
})();
html code is below
<input type="text" name="nametoprint" id="nametoprint" value="alex" />
<input type= "button" name="addtxt" id="addtxt" onclick="addText()" value="add text" />
<canvas id="maincanvas" style="border:1px solid #000;" width="400" height="450" ></canvas>
also i added this in fiddle here http://jsfiddle.net/pfgm8myp/
i want only allow to scale text object within the red bound box.moving object is working fine.i also added code for scaling but its not working.
can any one suggest me how to restrict this scaling ?
thanks in advance.

i posted it here with my own logic. http://jsfiddle.net/9xojfmyt/
i added below javascript code
(function(global) {
var canvas = new fabric.Canvas('maincanvas');
var yourNameObjs = [];
var goodtop =358, goodleft=250, boundingObject;
var boundingObject = new fabric.Rect({
left: 100,
top: 90,
width: 200,
height: 250,
opacity: 4,
selectable:false,
fill: 'red',
});
canvas.add(boundingObject);
addText = function(){
var nametoprint = $("#nametoprint").val();
canvas.remove(yourNameObjs);
yourNameObjs = new fabric.Text(nametoprint, {
left: 150, //Take the block's position
opacity: 9,
top: 150,
fontFamily: 'Delicious_500',
}
);
canvas.add(yourNameObjs);
// canvas moving limit
canvas.observe("object:moving", function(e){
var obj = yourNameObjs;
var bounds = boundingObject;
var objw = parseInt(parseInt(obj.width) * obj.scaleX);
var objh = parseInt(parseInt(obj.height) * obj.scaleY);
//left
if(obj.left < bounds.left){
obj.setLeft(bounds.left);
}
//top
if(obj.top < bounds.top){
obj.setTop(bounds.top);
}
//right
if((parseInt(obj.left) + objw) > (parseInt(bounds.left)+parseInt(bounds.width))){
obj.setLeft(((parseInt(bounds.left)+parseInt(bounds.width)) - objw));
}
//bottom
if((parseInt(obj.top) + objh) > (parseInt(bounds.top)+parseInt(bounds.height))){
obj.setTop(((parseInt(bounds.top)+parseInt(bounds.height)) - objh));
}
});
// canvas scaling limit
canvas.observe("object:scaling", function(e){
var obj = yourNameObjs;
var bounds = boundingObject;
var objw = parseInt(parseInt(obj.width) * obj.scaleX);
var objh = parseInt(parseInt(obj.height) * obj.scaleY);
//left
if(obj.left < bounds.left || (parseInt(obj.left) + objw) > (parseInt(bounds.left)+parseInt(bounds.width))){
obj.setLeft(bounds.left);
obj.setScaleX((bounds.width/obj.width));
}
//top
if(obj.top < bounds.top || (parseInt(obj.top) + objh) > (parseInt(bounds.top)+parseInt(bounds.height))){
obj.setTop(bounds.top);
obj.setScaleY((bounds.height/obj.height));
}
});
canvas.renderAll();
};
})();

Bit modification compare to Albert's answer. If you want to restrict object within canvas then replace moving event with below code.
// canvas scaling limit
canvas.observe("object:scaling", function(e){
var obj = e.target;
var bounds = {
left : 5, //here it keep 5 px margin from all direction
top : 5,
width : canvas.width - 10,
height : canvas.height - 10
};
var objw = parseInt(parseInt(obj.width) * obj.scaleX);
var objh = parseInt(parseInt(obj.height) * obj.scaleY);
//left
if(obj.left < bounds.left || (parseInt(obj.left) + objw) > (parseInt(bounds.left)+parseInt(bounds.width))){
obj.setLeft(bounds.left);
obj.setScaleX((bounds.width/obj.width));
}
//top
if(obj.top < bounds.top || (parseInt(obj.top) + objh) > (parseInt(bounds.top)+parseInt(bounds.height))){
obj.setTop(bounds.top);
obj.setScaleY((bounds.height/obj.height));
}
});

Related

How can i keep the Konvas layer centered after resize?

So i'm using Konvajs, to draw segments, only vertical and horizontal. My problem is the following, when i start the debug, if the window is small and then i click to maximize, the layer will stay with same size as when small. Ex:
here it s the Javascript file for the Konva creation:
function setUpDesignMap(){
var width = $('#designContainer').width();
var height = $('#designContainer').height();
var blockSnapSize = gridSize;
var isDrawing = false;
var drawShape = null;
designStage = new Konva.Stage({
container: 'designContainer',
width: width,
height: height,
draggable: true,
name:'designStage'
// dragBoundFunc: function(pos) {
// var newY = pos.y < 0 ? 0 : pos.y;
// var newX = pos.x < 0 ? 0 : pos.x;
// return {
// x: newX,
// y: newY
// };
// }
});
/*Set up grid*/
var gridLayer = new Konva.Layer();
var tipLayer = new Konva.Layer();
drawLayer = new Konva.Layer();
segGrp = new Konva.Group({name:'segments'});
/* draw grid */
for (var i = 0; i < 240; i++) {
gridLayer.add(new Konva.Line({
points: [Math.round(i * blockSnapSize), 0, Math.round(i * blockSnapSize), 120*blockSnapSize],
stroke: '#888',
strokeWidth: 0.5,
}));
}
gridLayer.add(new Konva.Line({points: [0,0,10,10]}));
for (var j = 0; j < 120; j++) {
gridLayer.add(new Konva.Line({
points: [0, Math.round(j * blockSnapSize), 240*blockSnapSize, Math.round(j * blockSnapSize)],
stroke: '#888',
strokeWidth: 0.5,
}));
}
/* Stage initial position*/
zoomVars_design = {scale: 1, factor: 1.1, origin: {x:-100*gridSize,y:-50*gridSize}};
designStage.position(zoomVars_design.origin);
designStage.scale({ x: 1, y: 1 });
/*Set up mouse tip*/
var mouseTip = new Konva.Rect({
width: 6,
height: 6,
opacity:0,
fill: '#FFCC00',
stroke: '#FFCC00'
});
tipLayer.add(mouseTip);
/* Set up length indicator */
var lenShape = new Konva.Line({
points:[],
opacity: 1,
strokeWidth: 1,
stroke: '#000',
dash:[7,5]
});
gridLayer.add(lenShape);
/* Set up length text*/
var lenText = new Konva.Text({
align: 'center',
verticalAlign: 'middle',
fontSize: 12
});
gridLayer.add(lenText);
var lenSide = new Konva.Line({
points:[],
opacity: 1,
strokeWidth: 1,
stroke: '#000',
dash:[7,5]
});
gridLayer.add(lenSide);
/*Mouse handlers*/
//remove default behaviour for the container
$("#designContainer").on('mousedown', function(e){
e.preventDefault();
});
$("#designContainer").on('contextmenu', function(e){
e.preventDefault();
});
//mouse handlers on stage
designStage.on('contentMousedown', function (e) {
if(e.evt.button == 0){
designStage.draggable(false);
}
else{
designStage.draggable(true);
return;
}
if(mouseMode == 0){ //draw mode
var startPoint = {x:(mouseTip.attrs.x+3), y:(mouseTip.attrs.y+3)};
if(!isDrawing){
if(!isIntersection(startPoint)){ //cannot be intersection to avoid crosses
//check if there's an intersection on the begining
if(isCorner(startPoint)){
intersectingSeg = false;
intersectingCorner = true;
console.log('intersectingSeg = false')
}
else{
//check if its on an existing segment
var isonseg = isOnSegment(startPoint);
if(isonseg.b){
//validate resulting segments length
var intercected = getSegmentsArray(isonseg.d)[isonseg.i];
var res = breakSegAtIntersection(startPoint,intercected);
if(isValidLength(res[0].start,res[0].end) && isValidLength(res[1].start,res[1].end)){
intersectingSeg = true;
intersectingCorner = false;
console.log('>>>>>>>Intersecting Segment at start')
}
else{
consoleAdd('Resulting segments are too small to you');
return;
}
}
}
//start drawing
drawShape = new Konva.Line({
points: [startPoint.x, startPoint.y],
strokeWidth: 15,
stroke: 'black',
opacity: 0.5,
dir:'h'
});
drawShape.on('mouseover', function (e) {
if(mouseMode == 1){
this.stroke('#031C4D');
drawLayer.draw();
}
});
drawShape.on('mouseout', function (e) {
if(mouseMode == 1){
this.stroke('black');
drawLayer.draw();
}
});
drawShape.on('mouseup', function (e) {
if(mouseMode == 1){
this.stroke('#092C70');
drawLayer.draw();
}
});
segGrp.add(drawShape);
drawLayer.add(segGrp);
drawLayer.draw();
isDrawing = true;
}
else{
consoleAdd('Cannot start on an intersection');
}
gridLayer.draw();
}
}
});
So i want the layer not to be that small, always refitting. Thank you all for your attention!
You need to manually resize your stage when the size of container element is changed.
window.addEventListener('resize', () => {
var width = $('#designContainer').width();
var height = $('#designContainer').height();
designStage.width(width);
designStage.height(height);
})
You may also need to apply the scale to the stage.
Take a look into related demo: https://konvajs.org/docs/sandbox/Responsive_Canvas.html

KonvaJS - Making it responsive with Bootstrap Modals

Below is a KonvaJS project where you can add stickers to an image. However, it has a fixed width and a fixed height.
Now because the sizes are fixed, it won't work with anything response, like a bootstrap modal.
Here is my attempt following a KonvaJS response guide, see here. and the guide here.
In my attempt, after I upload the image, my code can't get the new width of the modal as it returns 0, so it can't calculate it for the size of the canvas.
How can I make the canvas responsive?
function centreRectShape(shape) {
shape.x((stage.getWidth() - shape.getWidth()) / 2);
shape.y((stage.getHeight() - shape.getHeight()) / 2);
}
var stage = new Konva.Stage({
container: 'canvas-container',
width: 650,
height: 300
});
var layer = new Konva.Layer();
stage.add(layer);
var bgRect = new Konva.Rect({
width: stage.getWidth(),
height: stage.getHeight(),
fill: 'gold',
opacity: 0.1
});
layer.add(bgRect);
var uploadedImage = new Konva.Image({
draggable: false
});
layer.add(uploadedImage);
// make an object to keep things tidy - not strictly needed, just being tidy
function addSticker(imgUrl){
// make the sticker image object
var stickerObj = new Konva.Image({
x: 240,
y: 20,
width: 93,
height: 104,
name: 'sticker',
draggable: true
});
layer.add(stickerObj);
// make the sticker image loader html element
var stickerImage = new Image();
stickerImage.onload = function() {
stickerObj.image(stickerImage);
layer.draw();
};
stickerObj.on('transformstart', function(){
undoBefore = makeUndo(this);
})
stickerObj.on('transformend', function(){
var undoAfter = makeUndo(this);
addUndo(123, undoBefore, undoAfter)
})
// assigning the URL of the image starts the onload
stickerImage.src = imgUrl;
}
imgObj = new Image();
imgObj.onload = function() {
uploadedImage.image(imgObj);
var padding = 20;
var w = imgObj.width;
var h = imgObj.height;
var targetW = stage.getWidth() - (2 * padding);
var targetH = stage.getHeight() - (2 * padding);
var widthFit = targetW / w;
var heightFit = targetH / h;
var scale = (widthFit > heightFit) ? heightFit : widthFit;
w = parseInt(w * scale, 10);
h = parseInt(h * scale, 10);
uploadedImage.size({
width: w,
height: h
});
centreRectShape(uploadedImage);
layer.draw();
}
imgObj.src = 'https://images.pexels.com/photos/787961/pexels-photo-787961.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260';
$('.sticker').on('click', function() {
var theSticker = addSticker($(this).attr('src'));
toggle(true);
toggle(false);
});
var vis = false;
$('#toggler').on('click', function(){
toggle(vis);
})
function undoData(opts){
this.x = opts.x;
this.y = opts.y;
this.width = opts.w;
this.height = opts.h;
this.rotation = opts.r;
}
var undoBefore;
function makeUndo(shape){
return new undoData({x:shape.getX(), y: shape.getY(), w: shape.getWidth(), h: shape.getHeight(), r: shape.getRotation() })
}
var undoList = [];
function addUndo(shapeId, before, after){
undoList.push({id: shapeId, before: before, after: after});
console.log(undoList[undoList.length - 1])
}
function toggle(isVisible){
if (!isVisible){
var shapes = stage.find('.sticker');
shapes.each(function(shape) {
var imgRotator = new Konva.Transformer({
node: shape,
name: 'stickerTransformer',
keepRatio: true,
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right']
});
layer.add(imgRotator);
})
vis = true;
}
else {
var shapes = stage.find('.stickerTransformer');
shapes.each(function(shape) {
shape.remove();
})
vis=false;
}
layer.draw();
$('#toggler').html((vis ? 'Toggle Off' : 'Toggle On'));
}
html,
* {
margin: 0;
padding: 0;
}
body {
background: #eee;
}
#image-editor {
background: #fff;
border-radius: 3px;
border: 1px solid #d8d8d8;
width: 650px;
margin: 0 auto;
margin-top: 20px;
box-shadow: 0 3px 5px rgba(0, 0, 0, .2);
}
.stickers {
padding: 10px 5px;
background: #eee;
}
.stickers>img {
margin-right: 10px;
}
<div id="image-editor">
<div id="canvas-container"></div>
<div class="stickers">
<img class="sticker" src="https://craftblock.me/koa/fb-upload-clone/stickers/sticker%20(1).png" alt="Sticker" width="62px">
</div>
</div>
<script src="https://unpkg.com/konva#2.4.1/konva.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
So let me try to explain the issue a bit more: Check out the live version of my attempt of making it responsive.
As you can see, after trying to load the image into the canvas, the modal pops up but the canvas fails to resize.
Here's the JS to that:
/**
* Image Editor
*/
var stageWidth = 1000;
var stageHeight = 1000;
var stage = new Konva.Stage({
container: 'canvas-container',
width: stageWidth,
height: stageHeight
});
var layer = new Konva.Layer();
stage.add(layer);
var bgRect = new Konva.Rect({
width: stage.getWidth(),
height: stage.getHeight(),
fill: 'gold',
opacity: 0.1
});
layer.add(bgRect);
var uploadedImage = new Konva.Image({
draggable: false
});
layer.add(uploadedImage);
imgObj.onload = function () {
uploadedImage.image(imgObj);
var padding = 20;
var w = imgObj.width;
var h = imgObj.height;
var targetW = stage.getWidth() - (2 * padding);
var targetH = stage.getHeight() - (2 * padding);
var widthFit = targetW / w;
var heightFit = targetH / h;
var scale = (widthFit > heightFit) ? heightFit : widthFit;
w = parseInt(w * scale, 10);
h = parseInt(h * scale, 10);
uploadedImage.size({
width: w,
height: h
});
centreRectShape(uploadedImage);
layer.draw();
}
$('.sticker').on('click', function () {
addSticker($(this).attr('src'));
});
fitStageIntoParentContainer();
window.addEventListener('resize', fitStageIntoParentContainer);
function centreRectShape(shape) {
shape.x((stage.getWidth() - shape.getWidth()) / 2);
shape.y((stage.getHeight() - shape.getHeight()) / 2);
}
function addSticker(imgUrl) {
var stickerObj = new Konva.Image({
x: 240,
y: 20,
width: 93,
height: 104,
draggable: true
});
var stickerImage = new Image();
stickerImage.onload = function () {
stickerObj.image(stickerImage);
centreRectShape(stickerObj);
layer.draw();
};
stickerImage.src = imgUrl;
layer.add(stickerObj);
addModifiers(stickerObj);
}
function addModifiers(obj) {
var imgRotator = new Konva.Transformer({
node: obj,
keepRatio: true,
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right']
});
layer.add(imgRotator);
}
function fitStageIntoParentContainer() {
var container = document.querySelector("edit-image-modal");
// now we need to fit stage into parent
var containerWidth = container.offsetWidth;
// to do this we need to scale the stage
var scale = containerWidth / stageWidth;
stage.width(stageWidth * scale);
stage.height(stageHeight * scale);
stage.scale({
x: scale,
y: scale
});
stage.draw();
}
The technique you have used to listen for 'resize' on the page will work for the main window but likely not for the modal. You can confirm that by some simple console.log() output.
You need to use the bootstrap event on('show.bs.modal') to catch when the modal is shown, which is when you really want to fire the call to fitStageIntoParentContainer();
See this SO post for info. It is not a duplicate but covers the bootstrap modal event.
In case that question is erased, you should be heading for something like:
$('your_modal_element_selector').on('show.bs.modal', function () {
fitStageIntoParentContainer();
});

Prevent Fabric js Objects from scaling out of the canvas boundary

I have been trying to keep an object (constructed in fabric js over a canvas) inside the boundaries at all the times. It has been achieved at moving and rotating it. I took help from Move object within canvas boundary limit for achieving this. But when I start to scale the object, it simply keeps on going out of boundary. I do not understand what has to be done to keep it inside the boundary only, even while scaling. Please help me with a code to prevent this behavior. It would be great if you can attach a demo too.
<html>
<head>
<title>Basic usage</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.3/fabric.min.js"></script>
</head>
<body>
<canvas id="canvas" style= "border: 1px solid black" height= 480 width = 360></canvas>
<script>
var canvas = new fabric.Canvas('canvas');
canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 }));
canvas.item(0).set({
borderColor: 'gray',
cornerColor: 'black',
cornerSize: 12,
transparentCorners: true
});
canvas.setActiveObject(canvas.item(0));
canvas.renderAll();
canvas.on('object:moving', function (e) {
var obj = e.target;
// if object is too big ignore
if(obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width){
return;
}
obj.setCoords();
// top-left corner
if(obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0){
obj.top = Math.max(obj.top, obj.top-obj.getBoundingRect().top);
obj.left = Math.max(obj.left, obj.left-obj.getBoundingRect().left);
}
// bot-right corner
if(obj.getBoundingRect().top+obj.getBoundingRect().height > obj.canvas.height || obj.getBoundingRect().left+obj.getBoundingRect().width > obj.canvas.width){
obj.top = Math.min(obj.top, obj.canvas.height-obj.getBoundingRect().height+obj.top-obj.getBoundingRect().top);
obj.left = Math.min(obj.left, obj.canvas.width-obj.getBoundingRect().width+obj.left-obj.getBoundingRect().left);
}
});
</script>
</body>
</html>
My demo is attached here. :
https://jsfiddle.net/3v0cLaLk/
I was able to solve the problem as follows:
var canvas = new fabric.Canvas('canvas');
canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 }));
canvas.item(0).set({
borderColor: 'gray',
cornerColor: 'black',
cornerSize: 12,
transparentCorners: true
});
canvas.setActiveObject(canvas.item(0));
canvas.renderAll();
canvas.on('object:moving', function (e) {
var obj = e.target;
// if object is too big ignore
if(obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width){
return;
}
obj.setCoords();
// top-left corner
if(obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0){
obj.top = Math.max(obj.top, obj.top-obj.getBoundingRect().top);
obj.left = Math.max(obj.left, obj.left-obj.getBoundingRect().left);
}
// bot-right corner
if(obj.getBoundingRect().top+obj.getBoundingRect().height > obj.canvas.height || obj.getBoundingRect().left+obj.getBoundingRect().width > obj.canvas.width){
obj.top = Math.min(obj.top, obj.canvas.height-obj.getBoundingRect().height+obj.top-obj.getBoundingRect().top);
obj.left = Math.min(obj.left, obj.canvas.width-obj.getBoundingRect().width+obj.left-obj.getBoundingRect().left);
}
});
var left1 = 0;
var top1 = 0 ;
var scale1x = 0 ;
var scale1y = 0 ;
var width1 = 0 ;
var height1 = 0 ;
canvas.on('object:scaling', function (e){
var obj = e.target;
obj.setCoords();
var brNew = obj.getBoundingRect();
if (((brNew.width+brNew.left)>=obj.canvas.width) || ((brNew.height+brNew.top)>=obj.canvas.height) || ((brNew.left<0) || (brNew.top<0))) {
obj.left = left1;
obj.top=top1;
obj.scaleX=scale1x;
obj.scaleY=scale1y;
obj.width=width1;
obj.height=height1;
}
else{
left1 =obj.left;
top1 =obj.top;
scale1x = obj.scaleX;
scale1y=obj.scaleY;
width1=obj.width;
height1=obj.height;
}
});
<html>
<head>
<title>Basic usage</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.3/fabric.min.js"></script>
</head>
<body>
<canvas id="canvas" style= "border: 1px solid black" height= 480 width = 360></canvas>
</body>
</html>
You can set on object modified listener and check if object is out of bounds. If so, then restore it to its original state.
this.canvas.on('object:modified', function (options: any) {
let obj = options.target;
let boundingRect = obj.getBoundingRect(true);
if (boundingRect.left < 0
|| boundingRect.top < 0
|| boundingRect.left + boundingRect.width > scope.canvas.getWidth()
|| boundingRect.top + boundingRect.height > scope.canvas.getHeight()) {
obj.top = obj._stateProperties.top;
obj.left = obj._stateProperties.left;
obj.angle = obj._stateProperties.angle;
obj.scaleX = obj._stateProperties.scaleX;
obj.scaleY = obj._stateProperties.scaleY;
obj.setCoords();
obj.saveState();
}
});
If you want to perform a real time prevention, you should use object:scaling event, as object:modified is only triggered at the end of the transformation.
1) Add event handler to canvas:
this.canvas.on('object:scaling', (e) => this._handleScaling(e));
2) In the handler function, get the old and the new object's bounding rect:
_handleScaling(e) {
var obj = e.target;
var brOld = obj.getBoundingRect();
obj.setCoords();
var brNew = obj.getBoundingRect();
3) For each border, check if object has scaled beyond the canvas boundaries and compute its left, top and scale properties:
// left border
// 1. compute the scale that sets obj.left equal 0
// 2. compute height if the same scale is applied to Y (we do not allow non-uniform scaling)
// 3. compute obj.top based on new height
if(brOld.left >= 0 && brNew.left < 0) {
let scale = (brOld.width + brOld.left) / obj.width;
let height = obj.height * scale;
let top = ((brNew.top - brOld.top) / (brNew.height - brOld.height) *
(height - brOld.height)) + brOld.top;
this._setScalingProperties(0, top, scale);
}
4) Similar code for the other borders:
// top border
if(brOld.top >= 0 && brNew.top < 0) {
let scale = (brOld.height + brOld.top) / obj.height;
let width = obj.width * scale;
let left = ((brNew.left - brOld.left) / (brNew.width - brOld.width) *
(width - brOld.width)) + brOld.left;
this._setScalingProperties(left, 0, scale);
}
// right border
if(brOld.left + brOld.width <= obj.canvas.width
&& brNew.left + brNew.width > obj.canvas.width) {
let scale = (obj.canvas.width - brOld.left) / obj.width;
let height = obj.height * scale;
let top = ((brNew.top - brOld.top) / (brNew.height - brOld.height) *
(height - brOld.height)) + brOld.top;
this._setScalingProperties(brNew.left, top, scale);
}
// bottom border
if(brOld.top + brOld.height <= obj.canvas.height
&& brNew.top + brNew.height > obj.canvas.height) {
let scale = (obj.canvas.height - brOld.top) / obj.height;
let width = obj.width * scale;
let left = ((brNew.left - brOld.left) / (brNew.width - brOld.width) *
(width - brOld.width)) + brOld.left;
this._setScalingProperties(left, brNew.top, scale);
}
5) If object's BoundingRect has crossed canvas boundaries, fix its position and scale:
if(brNew.left < 0
|| brNew.top < 0
|| brNew.left + brNew.width > obj.canvas.width
|| brNew.top + brNew.height > obj.canvas.height) {
obj.left = this.scalingProperties['left'];
obj.top = this.scalingProperties['top'];
obj.scaleX = this.scalingProperties['scale'];
obj.scaleY = this.scalingProperties['scale'];
obj.setCoords();
} else {
this.scalingProperties = null;
}
}
6) Finally, when setting the scaling properties, we have to stick with the smallest scale in case the object has crossed more than one border:
_setScalingProperties(left, top, scale) {
if(this.scalingProperties == null
|| this.scalingProperties['scale'] > scale) {
this.scalingProperties = {
'left': left,
'top': top,
'scale': scale
};
}
}
Below is the code for blocking the coordinates of any object outside the canvas area from all directions
canvas.on('object:modified', function (data) {
var currentObject = data.target;
var tempObject = angular.copy(data.target);
var canvasMaxWidth = canvas.width - 20,
canvasMaxHeight = canvas.height - 20;
var actualWidth = currentObject.getBoundingRect().width,
actualHeight = currentObject.getBoundingRect().height;
if (actualHeight > canvasMaxHeight) {
currentObject.scaleToHeight(canvasMaxHeight);
currentObject.setCoords();
canvas.renderAll();
if (tempObject.scaleX < currentObject.scaleX) {
currentObject.scaleX = tempObject.scaleX;
currentObject.setCoords();
canvas.renderAll();
}
if (tempObject.scaleY < currentObject.scaleY) {
currentObject.scaleY = tempObject.scaleY;
currentObject.setCoords();
canvas.renderAll();
}
if (currentObject.getBoundingRectHeight() < canvasMaxHeight - 50) {
currentObject.scaleX = (currentObject.scaleX * canvasMaxHeight) / (currentObject.scaleX * currentObject.width);
currentObject.setCoords();
canvas.renderAll();
}
}
if (actualWidth > canvasMaxWidth) {
currentObject.scaleToWidth(canvasMaxWidth);
obj.setCoords();
canvas.renderAll();
if (tempObject.scaleX < currentObject.scaleX) {
currentObject.scaleX = tempObject.scaleX;
currentObject.setCoords();
canvas.renderAll();
}
if (tempObject.scaleY < currentObject.scaleY) {
currentObject.scaleY = tempObject.scaleY;
currentObject.setCoords();
canvas.renderAll();
}
}
obj.setCoords();
canvas.renderAll();
});
I was able to block movement outside of boundaries using the Bounding box in the following way using the last version of Fabric ("fabric": "^4.6.0") & Typescript:
private boundingBox: fabric.Rect = null;
this.setBoundingBox(width, height);
private setBoundingBox(width: number, height: number) {
this.boundingBox = new fabric.Rect({
name: OBJECT_TYPE.BOUNDING_BOX,
fill: DEFINITIONS.BG_COLOR,
width: width,
height: height,
hasBorders: false,
hasControls: false,
lockMovementX: true,
lockMovementY: true,
selectable: false,
evented: false,
stroke: 'red',
});
this._canvas.add(this.boundingBox);
}
this._canvas.on('object:moving', (e) => {
console.log('object:moving');
this._avoidObjectMovingOutsideOfBoundaries(e);
});
private _avoidObjectMovingOutsideOfBoundaries(e: IEvent) {
let obj = e.target;
const top = obj.top;
const bottom = top + obj.height;
const left = obj.left;
const right = left + obj.width;
const topBound = this.boundingBox.top;
const bottomBound = topBound + this.boundingBox.height;
const leftBound = this.boundingBox.left;
const rightBound = leftBound + this.boundingBox.width;
obj.left = Math.min(Math.max(left, leftBound), rightBound - obj.width);
obj.top = Math.min(Math.max(top, topBound), bottomBound - obj.height);
return obj;
}
Any additional extensions for Scaling objects are welcome.
canvas.on('object:scaling', function (e) {
var obj = e.target;
obj.setCoords();
let top = obj.getBoundingRect().top;
let left = obj.getBoundingRect().left;
let height = obj.getBoundingRect().height;
let width = obj.getBoundingRect().width;
// restrict scaling below bottom of canvas
if (top + height > CANVAS_HEIGHT) {
obj.scaleY = 1;
obj.setCoords();
let h = obj.getScaledHeight();
obj.scaleY = (CANVAS_HEIGHT - top) / h;
obj.setCoords();
canvas.renderAll();
obj.lockScalingX = true;
obj.lockScalingY = true;
obj.lockMovementX = true;
obj.lockMovementY = true;
}
// restrict scaling above top of canvas
if (top < 0) {
obj.scaleY = 1;
obj.setCoords();
let h = obj.getScaledHeight();
obj.scaleY = (height + top) / h;
obj.top = 0;
obj.setCoords();
canvas.renderAll();
obj.lockScalingX = true;
obj.lockScalingY = true;
obj.lockMovementX = true;
obj.lockMovementY = true;
}
// restrict scaling over right of canvas
if (left + width > CANVAS_WIDTH) {
obj.scaleX = 1;
obj.setCoords();
let w = obj.getScaledWidth();
obj.scaleX = (CANVAS_WIDTH - left) / w;
obj.setCoords();
canvas.renderAll();
obj.lockScalingX = true;
obj.lockScalingY = true;
obj.lockMovementX = true;
obj.lockMovementY = true;
}
// restrict scaling over left of canvas
if (left < 0) {
obj.scaleX = 1;
obj.setCoords();
let w = obj.getScaledWidth();
obj.scaleX = (width + left) / w;
obj.left = 0;
obj.setCoords();
canvas.renderAll();
obj.lockScalingX = true;
obj.lockScalingY = true;
obj.lockMovementX = true;
obj.lockMovementY = true;
}
});
canvas.on('object:modified', function (event) {
// after text object is done with modifing e.g. resizing or moving
if (!!event.target) {
event.target.lockScalingX = false;
event.target.lockScalingY = false;
event.target.lockMovementX = false;
event.target.lockMovementY = false;
}
})

kinetic js textPath: draggable didn't work

var stages = new Array() ;
var limites = new Array() ;
numStage=0;
r = {
'x':65,
'y':120,
'xwidth':335,
'yheight':210
};
limites.push(r);
stages[numStage] = new Kinetic.Stage({
container: 'cmg_canvas_'+numStage,
width: 450,
height: 450
});
//creation image
obj = new Image();
obj.src = 'http://i.imgur.com/zFZgKuS.jpg';
image = new Kinetic.Image({
image: obj,
width: 450,
height: 450
});
// add image to calque
layer = new Kinetic.Layer();
layer.add(image);
stages[numStage].add(layer); //add image to canvas
layer = new Kinetic.Layer();
//set limit x y h l
/*var rect = new Kinetic.Rect({
name: 'limite',
x: limites[numStage].x,
y: limites[numStage].y,
width: limites[numStage].xwidth,
height: limites[numStage].yheight,
stroke: 'black',
strokeWidth: 0.5
});*/
//layer.add(rect);// add to canvas
stages[numStage].add(layer);
$('.cmg_text').live('blur', function(){
idText = 'cmg_line0';
numStage = 0;
drawTextPath(numStage, idText,$(this).val(),50,22,numStage);
//text = getText(this).text;
});
function getSVG(x,y,w,verif) {
halfw = parseFloat((w/2).toFixed(2));
x1 = parseFloat((halfw/2).toFixed(2));
x2 = parseFloat(halfw + x1);
if(parseInt(verif))
{
y1 = parseFloat(y) * 2 +18;
y2 = parseFloat(y) * 2 +18;
}
else
{
y1 = -18;
y2 = -18;
}
str = 'M '+x+','+y+' C '+x1+','+y1+' '+x2+','+y2+' '+w+','+y;
return str;
}
function drawTextPath(numStage, textId,text,valueSlider, newFontSize,numStage){
//'M 0,115 C42,-18 126,-18 165,115';
//'M 0,115 C45,230 180,230 180,115';
var arcOnly = 0;
if(textId == 'cmg_line0')
{
console.log('limites[numStage].yheight/2'+limites[numStage].yheight/2);
console.log('limites[numStage].xwidth'+limites[numStage].xwidth);
svg = getSVG(0,valueSlider,valueSlider*6.3,0);
arcOnly = 0;
}
//alert(svg);
console.log(parseFloat(limites[numStage].y));
console.log(parseFloat(arcOnly));
console.log(parseFloat(limites[numStage].y - arcOnly));
var layer = new Kinetic.Layer({name:'textPathLayer',draggable:true});
var textpath = new Kinetic.TextPath({
name:'TextPath',
id: textId,
//x: 0,
//x: limites[numStage].x + limites[numStage].xwidth/2,
//y: 0,
//y: limites[numStage].y + limites[numStage].yheight/2,
x: limites[numStage].x ,
y: limites[numStage].y + limites[numStage].yheight/2,
fill: '#000',
fontSize: newFontSize,
fontFamily: 'Arial',
text: text,
//offsetX:0,
//offsetY:0,
draggable: true,
dragBoundFunc: function(pos){
p = textParams(this, pos);
return {x: p.newX, y: p.newY};
},
data: svg
});
//
layer.add(textpath);
stages[numStage].add(layer);
//layer.moveToTop();
//layer.draw();
//stages[0].draw();
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/kineticjs/4.6.0/kinetic.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<div id="cmg_canvas_0"></div>
<input type='text' class='cmg_text' />
I have to draw a draggable textpath with kineticjs, with text given by an input text, the action is triggered in blur.
I have a stage that contain 3 layers;
Layer for the background, and one layer for the textpath.
My problem that the draggable in the textpath is not working,
i tried to set the text layer in the top, but i didn't get it draggable.
This is my jsfiddle
I have a doubt of inner layer problem.
Thanks in advance.

Collision detection using kineticJS (getIntersection function not working)

I am trying to recreate the game http://www.sinuousgame.com/ and started studying html5 canvas and kineticJS.
Recently i came across the getIntersection function and coudnt find much details regarding it.But with what i had ,i did make a code to get the Collision detection done using getIntersection() function.
But it doesnt seem to be working.
As you can see, My Fiddle: http://jsfiddle.net/p9fnq/8/
//The working player code
var LimitedArray = function(upperLimit) {
var storage = [];
// default limit on length if none/invalid supplied;
upperLimit = +upperLimit > 0 ? upperLimit : 100;
this.push = function(item) {
storage.push(item);
if (storage.length > upperLimit) {
storage.shift();
}
return storage.length;
};
this.get = function(flag) {
return storage[flag];
};
this.iterateItems = function(iterator) {
var flag, l = storage.length;
if (typeof iterator !== 'function') {
return;
}
for (flag = 0; flag < l; flag++) {
iterator(storage[flag]);
}
};
};
var tail = new LimitedArray(50);
var flag = 0, jincr = 0;
var stage = new Kinetic.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
listening: true
});
var layer = new Kinetic.Layer({
listening: true
});
stage.add(layer);
var player = new Kinetic.Circle({
x: 20,
y: 20,
radius: 6,
fill: 'cyan',
stroke: 'black',
draggable: true
});
var line = new Kinetic.Line({
points: [],
stroke: 'cyan',
strokeWidth: 2,
lineCap: 'round',
lineJoin: 'round'
});
layer.add(line);
layer.add(player);
// move the circle with the mouse
stage.getContent().addEventListener('mousemove', function() {
player.position(stage.getPointerPosition());
var obj = {
x: stage.getPointerPosition().x,
y: stage.getPointerPosition().y
};
tail.push(obj);
var arr = [];
tail.iterateItems(function(p) {
arr.push(p.x, p.y);
});
line.points(arr);
});
var x = 0;
var y = 0;
var noOfEnemies = 200;
var enemyArmada = new Array();
createEnemy();
function createEnemy() {
for (var i = 0; i < noOfEnemies; i++) {
var enemy = new Kinetic.Circle({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
radius: 4.5 + 1.5 * Math.random(),
fill: 'red',
stroke: 'black'
});
enemy.speedX = enemy.speedY = (0.5 + Math.random() * 50);
enemyArmada.push(enemy);
layer.add(enemy);
}
}
var checkCollide = function() {
var position = stage.getPointerPosition();
if(position == null)
position = player.position();
if(position == null)
position = {x:0,y:0};
var collided = stage.getIntersection(position);
console.log(position);
if (typeof collided !== 'Kinetic.Shape') {
console.log("not shape");
}
else {
console.log("BOOOM!!!");
}
};
var anim = new Kinetic.Animation(function(frame) {
checkCollide();
for (var i = 0; i < noOfEnemies; i++) {
var e = enemyArmada[i];
e.position({
x: e.position().x - e.speedX * (frame.timeDiff / 400),
y: e.position().y + e.speedY * (frame.timeDiff / 400)
});
if (e.position().y < 0 || e.position().x < 0) {
e.position({
x: (Math.random() * (window.innerWidth + 600)),
y: -(Math.random() * window.innerHeight)
});
}
}
}, layer);
anim.start();
I need the collision to be detected. The function i have written here is checkCollide and its called within the kinetic.Animation function.
Can anyone help me out with this??
(If you don't know the solution,please do like the post,i need the solution badly)
The source of the problem
getIntersection(point) means "is any object at this point".
Since the point you're using is the player's position, getIntersection will always return true because player is always at its own position !
One solution
Put your player on one layer and all enemies on a separate layer.
That way you can hit test the enemy layer without the interference of the player object.
Code and a Demo: http://jsfiddle.net/m1erickson/JCfW8/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.0.1.min.js"></script>
<style>
body{padding:20px;}
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:350px;
height:350px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 350
});
var enemyLayer = new Kinetic.Layer();
stage.add(enemyLayer);
var playerLayer = new Kinetic.Layer();
stage.add(playerLayer);
var player = new Kinetic.Circle({
x:100,
y:100,
radius: 10,
fill: 'green',
draggable: true
});
player.on("dragmove",function(){
if(enemyLayer.getIntersection(player.position())){
this.fill("red");
playerLayer.draw();
}
});
playerLayer.add(player);
playerLayer.draw();
var enemy = new Kinetic.Circle({
x:200,
y:100,
radius: 20,
fill: 'blue',
draggable: true
});
enemyLayer.add(enemy);
enemyLayer.draw();
}); // end $(function(){});
</script>
</head>
<body>
<h4>Drag the green player<br>Player will turn red if it collides<br>with the blue enemy</h4>
<div id="container"></div>
</body>
</html>
Another solution
Mathematically test the player against every enemy:
Warning: untested code--some tweaking might be required
function playerEnemyCollide(){
var playerX=player.x();
var playerY=player.y();
var playerRadius=player.radius();
for(var i=0;i<enemyArmada.length;i++){
var e=enemyArmada[i];
if(circlesColliding(playerX,playerY,playerRadius,e.x,e.y,e.radius)){
return(true);
}
}
return(false);
}
function circlesColliding(cx1,cy1,radius1,cx2,cy2,radius2){
var dx=cx2-cx1;
var dy=cy2-cy1;
return(dx*dx+dy*dy<(radius1*2+radius2*2);
}

Categories