Not sure if this is exactly the correct approach for this but I have an array of buttons that spawn on the screen and after a few seconds it changes location and I have to click the buttons in the order they appeared initially.
Following is the code:
let arrayButtons = [];
let goButton = document.getElementById("inputButton");
function Buttons(color, height, width, top, left, order) {
this.order = order;
this.btn = document.createElement("button");
this.btn.style.backgroundColor = color;
this.btn.style.height = height;
this.btn.style.width = width;
this.btn.style.position = "absolute";
document.body.appendChild(this.btn);
this.setLocation = function(top, left) {
this.btn.style.top = top;
this.btn.style.left = left;
};
this.setLocation(top, left);
}
function createButtons(numOfButtons) {
console.log(numOfButtons);
let color;
let heightVal;
let widthVal;
let top;
let left;
let currentButton;
for (let i = 0; i < numOfButtons; i++) {
color = "#" + genRandomColor();
heightVal = "60px";
widthVal = "120px";
let x = window.innerWidth - 100;
let y = window.innerHeight - 100;
top = 90 + "px";
left = i * 120 + "px";
currentButton = new Buttons(color, heightVal, widthVal, top, left, i);
arrayButtons.push(currentButton);
}
}
function genRandomColor() {
let randomColor = Math.floor(Math.random() * 16777215).toString(16);
return randomColor;
}
function change() {
setTimeout(function() {
let x = window.innerWidth - 100;
let y = window.innerHeight - 200;
for (let i = 0; i < arrayButtons.length; i++) {
randomX = Math.floor(Math.random() * x + 50);
randomY = Math.floor(Math.random() * y + 50);
arrayButtons[i].setLocation(
randomX + 'px',
randomY + 'px');
}
}, 5000);
}
function isValid(range) {
range = document.getElementById("textField").value;
if (range < 2 || range > 10) {
return false;
} else {
return true;
}
}
// idea.. if the user clicks on the button in the order of the array it works
// for the hint, just make the value equal to the index number + 1
function clickHandler() {
let minMax = isValid();
if (minMax == true) {
createButtons(document.getElementById("textField").value);
change();
} else {
window.alert("Must be between 2 and 10");
}
}
goButton.onclick = clickHandler;
<p>How Many Buttons To Create?</p>
<input id="textField" type="text">
<button type="button" id="inputButton">Go!</button>
<br>
<br>
HTML
<button class="buttonClass">1</button>
JS
let btns = document.querySelectorAll(".buttonClass");
let currentBtn = 0;
btns.forEach(function(btn, crtIndex){
btn.onclick = function(){
if(currentBtn === crtIndex){
currentBtn++;
console.log("correct");
}
};
});
i am trying to absolute position images at random positions inside a div. i'm not sure how to get the calculations right for 'top' and 'left' but the images on occasions display outside of the div. i want to also prevent the overlapping of the images.
Any ideas would help
(function() {
//array of links to the images
var images = ["http://via.placeholder.com/150/800",
"http://via.placeholder.com/150/080",
"http://via.placeholder.com/150/008",
"http://via.placeholder.com/150/880"
];
//function to calculate a random integer
var getRandomInt = function(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
//function to get a top and left value position for each image
var pos = function() {
var wrapWidth = document.getElementById("wrap");
wrapWidth = $("#wrap").width();
wrapHeight = $("#wrap").height();
// Image Position
var xPos = getRandomInt(0, wrapWidth - 150);
var yPos = getRandomInt(0, wrapHeight - 150);
return {
x: xPos + "px",
y: yPos + "px"
}
}
var displayImages = function(images) {
var elementArray = [];
for (var i = 0; i < images.length; i++) {
var src = images[i];
elementArray[i] = '<img class="imagePos" style="top:' + pos().x + '; left:' + pos().y + ' " src="' + src + ' "/>';
}
console.log(elementArray);
elementArray.forEach(function(element) {
console.log(element);
$("#wrap").append(element);
});
}
displayImages(images);
})();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="IntTree">
<div id="header">
<h1>Interactive Tree</h1>
</div>
<div id="wrap">
</div>
</div>
I'm assuming that you have some css resembling this:
img {
position: absolute;
width:150;
height:150;
}
Regarding your first issue, you appear to have your x and y assignments backwards in the bit where you're adding the elems to the array. Also, you're making 2 times as many calls to pos() as needed.
That line should be:
let position = pos();
elementArray[i] = '<img class="imagePos" style="top:'+position.y+'; left:'+position.x+' " src="'+src+' "/>';
For the second issue, you need to check for each image whether any of the corners overlap a different image. The easy way to achieve this by adding an array to track the positions you've already used, and comparing against the items in the array for subsequent position calculations.
(function (){
//array of links to the images
var images = ["http://via.placeholder.com/150/800",
"http://via.placeholder.com/150/080",
"http://via.placeholder.com/150/008",
"http://via.placeholder.com/150/880"
];
//function to calculate a random integer
var getRandomInt = function (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// array to track previous positions of images
var positions = [];
//function to get a top and left value position for each image
var pos = function (){
var wrapWidth = $("#wrap").width();
var wrapHeight = $("#wrap").height();
// Image Position
var xPos = getRandomInt(0, wrapWidth - 150);
var yPos = getRandomInt(0, wrapHeight - 150);
var overlapX = true;
var overlapY = true;
while(overlapX && overlapY) {
overlapX = false;
overlapY = false;
for(var i = 0; i < positions.length; i++) {
// check if x coord is inside previously placed image
if ( (xPos > positions[i].x && xPos < positions[i].x+150) ||
(xPos+150 > positions[i].x && (xPos+150) < positions[i].x+150) ){
overlapX = true;
}
// check if y coord is inside previously placed image
if( (yPos > positions[i].y && yPos < positions[i].y+150) ||
(yPos+150 > positions[i].y && yPos+150 < positions[i].y+150) ) {
overlapY = true;
}
}
if (overlapX) {
xPos = getRandomInt(0, wrapWidth - 150);
}
if (overlapY) {
yPos = getRandomInt(0, wrapHeight - 150);
}
}
positions.push({x:xPos,y:yPos});
return {
x: xPos + "px",
y: yPos + "px"
}
}
var displayImages = function(images){
var elementArray = [];
for (var i = 0; i < images.length; i++) {
var src = images[i];
let position = pos();
elementArray[i] = '<img class="imagePos" style="top:'+position.y+'; left:'+position.x+' " src="'+src+' "/>';
}
elementArray.forEach(function(element) {
$("#wrap").append(element);
});
}
displayImages(images);
})();
When I test or publish my project, which is a jigsaw puzzle from this tutorial: https://www.youtube.com/watch?v=uCQuUZs3UGE
Nothing gets drawn except the grey background. No puzzle pices nor any puzzle shape is drawn at all. I also get a warning:
WARNINGS:
Frame numbers in EaselJS start at 0 instead of 1. For example, this affects gotoAndStop and gotoAndPlay calls. (17)
But that should just be a warning, not an error. So might there be an error in my javascript code? This is the code, its fairly simple:
//************
// Initialize;
var numPieces = 16;
for (var i = 0; i = < numPieces; i++)
{
var pieceName = "p" + (i + 1);
var piece = this[pieceName];
if (piece)
{
piece.name = pieceName;
piece.on("mousedown", function(evt)
{
this.scaleX = 1;
this.scaleY = 1;
this.shadow = null;
this.parent.addChild(this);
this.offset = (x:this.x - evt.stageX, y:this.y - evt.stageY);
});
piece.on("pressmove", function (evt)
{
this.x = evt.stageX + this.offset.x;
this.y = evt.stageY + this.offset.y;
});
piece.on("pressup", function(evt)
{
var target = this.parent["t"+this.name.substr(1)];
if (target && hitTestInRange(target, 30) )
{
this.x = target.x;
this.y = target.y;
}
});
}
}
function hitTestInRange( target, range )
{
if (target.x > stage.mouseX - range &&
target.x < stage.mouseX + range &&
target.y > stage.mouseY - range &&
target.y < stage.mouseY + range)
{
return true;
}
return false;
}
I also included a screenshot of the project. screenshot
Thanks in advance
I am trying to create a simple javascript animation that will expand the width of an element from 0px to a specified width. Although this is probably a pretty elementary problem for some, I just can't seem to figure out what I am doing wrong. Instead of the loop running and expanding the div as desired I am only able to expand the div by repeatedly firing the function manually.
Fiddle
Current js code:
var el = document.getElementById('h');
var btn = document.getElementById('button1');
var oswidth = el.offsetWidth;
var target_width = 100;
btn.onclick = function grow() {
var loop = setTimeout('grow(\'' + el + '\')', 20);
if (oswidth < target_width) {
oswidth += 5;
} else {
clearTimeout(loop);
}
el.style.width = oswidth + 'px';
}
And as always, thank you in advance!
Try this code.fiddle-https://jsfiddle.net/L8kLx7d2/
var el = document.getElementById('h');
var btn = document.getElementById('button1');
var oswidth = el.offsetWidth;
var target_width = 100;
var loop;
function grow() {
if (oswidth < target_width) {
oswidth += 5;
} else {
clearInterval(loop);
}
el.style.width = oswidth + 'px';
}
btn.onclick=function(){
loop=setInterval(grow, 1000)};
Try this:
var el = document.getElementById('h');
var btn = document.getElementById('button1');
var oswidth = el.offsetWidth;
var target_width = 100;
btn.onclick = function grow() {
if (oswidth < target_width) {
oswidth += 5;
el.style.width = oswidth + 'px';
setTimeout(grow, 20);
}
}
I'll leave it to you to puzzle over the differences.
You can call the grow function in setInterval
JS:
var el = document.getElementById('h');
var btn = document.getElementById('button1');
var oswidth = el.offsetWidth;
var target_width = 100;
function grow() {
var loop = setTimeout('grow(\'' + el + '\')', 20);
if (oswidth < target_width) {
oswidth += 5;
} else {
clearTimeout(loop);
}
el.style.width = oswidth + 'px';
}
setInterval(grow, 1000);
Demo: http://jsfiddle.net/lotusgodkk/jeq834de/1/
There are three solutions. The first is using recursive setTimeout:
function grow(time) {
if (oswidth < target_width) {
oswidth += 5;
el.style.width = oswidth + 'px';
setTimeout(function() {grow(time);}, time);
}
}
The second is to use setInterval as described in the answer of K K.
The third is my personal favorite, using the transition css attribute.
My goal is to prevent overlapping of two or more rectangles inside my FabricJS canvas.
Imagine two rectangles having info on position and size and you can drag and drop any rectangle inside the canvas.
If rectangle A gets close enough to rectangle B, the position of rectangle A should snap to the edge of rectangle B. This should work for any edge of rectangle B. The vertices do not have to match, cause the sizes of the rectangles are variable.
I have a working example for this snapping on one dimension (x-axes).
My best jsfiddle attempt
See jsfiddle.
But I need it to work around the rectangle on both dimensions. I am quite sure, that my code is not well enough to manage this.
Code-snippets which might help:
object.oCoords.tl.x //top-left corner x position. similar goes for top-right (tr), bottom-left (bl), bottom-right (br) and .y for y-position
mouse_pos = canvas.getPointer(e.e);
mouse_pos.x //pointer x.position
mouse_pos.y //pointer y.position
object.intersectsWithObject(targ) // object = dragged rectangle, targ = targeted rectangle
The snapping should work for an unlimited amount of objects (not only for two rectangles).
I solved the problem on my own.
See jsfiddle: http://jsfiddle.net/gcollect/FD53A/
This is the code:
this.canvas.on('object:moving', function (e) {
var obj = e.target;
obj.setCoords(); //Sets corner position coordinates based on current angle, width and height
canvas.forEachObject(function (targ) {
var objects = this.canvas.getObjects(),
i = objects.length;
activeObject = canvas.getActiveObject();
if (targ === activeObject) return;
if (Math.abs(activeObject.oCoords.tr.x - targ.oCoords.tl.x) < edgedetection) {
activeObject.left = targ.left - activeObject.currentWidth;
}
if (Math.abs(activeObject.oCoords.tl.x - targ.oCoords.tr.x) < edgedetection) {
activeObject.left = targ.left + targ.currentWidth;
}
if (Math.abs(activeObject.oCoords.br.y - targ.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top - activeObject.currentHeight;
}
if (Math.abs(targ.oCoords.br.y - activeObject.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top + targ.currentHeight;
}
if (activeObject.intersectsWithObject(targ) && targ.intersectsWithObject(activeObject)) {
targ.strokeWidth = 10;
targ.stroke = 'red';
} else {
targ.strokeWidth = 0;
targ.stroke = false;
}
if (!activeObject.intersectsWithObject(targ)) {
activeObject.strokeWidth = 0;
activeObject.stroke = false;
}
});
Works pretty legit! Cheers!
This is based on gco's answer, updated to work with FabricJS 1.5.0, with the following improvements:
Shapes don't overlap.
Snapping is more responsive.
Shapes are contained within the canvas.
JS Fiddle: https://jsfiddle.net/aphillips8/31qbr0vn/1/
var canvas = new fabric.Canvas('canvas'),
canvasWidth = document.getElementById('canvas').width,
canvasHeight = document.getElementById('canvas').height,
counter = 0,
rectLeft = 0,
snap = 20; //Pixels to snap
canvas.selection = false;
plusrect();
plusrect();
plusrect();
function plusrect(top, left, width, height, fill) {
var rect = new fabric.Rect({
top: 300,
name: 'rectangle ' + counter,
left: 0 + rectLeft,
width: 100,
height: 100,
fill: 'rgba(' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ', 0.75)',
lockRotation: true,
originX: 'left',
originY: 'top',
cornerSize: 15,
hasRotatingPoint: false,
perPixelTargetFind: true,
minScaleLimit: 1,
maxWidth: canvasWidth,
maxHeight: canvasHeight
});
rect.custom = {};
rect.custom.counter = counter;
canvas.add(rect);
counter++;
rectLeft += 200;
}
function findNewPos(distX, distY, target, obj) {
// See whether to focus on X or Y axis
if(Math.abs(distX) > Math.abs(distY)) {
if (distX > 0) {
target.setLeft(obj.getLeft() - target.getWidth());
} else {
target.setLeft(obj.getLeft() + obj.getWidth());
}
} else {
if (distY > 0) {
target.setTop(obj.getTop() - target.getHeight());
} else {
target.setTop(obj.getTop() + obj.getHeight());
}
}
}
canvas.on('object:moving', function (options) {
// Sets corner position coordinates based on current angle, width and height
options.target.setCoords();
// Don't allow objects off the canvas
if(options.target.getLeft() < snap) {
options.target.setLeft(0);
}
if(options.target.getTop() < snap) {
options.target.setTop(0);
}
if((options.target.getWidth() + options.target.getLeft()) > (canvasWidth - snap)) {
options.target.setLeft(canvasWidth - options.target.getWidth());
}
if((options.target.getHeight() + options.target.getTop()) > (canvasHeight - snap)) {
options.target.setTop(canvasHeight - options.target.getHeight());
}
// Loop through objects
canvas.forEachObject(function (obj) {
if (obj === options.target) return;
// If objects intersect
if (options.target.isContainedWithinObject(obj) || options.target.intersectsWithObject(obj) || obj.isContainedWithinObject(options.target)) {
var distX = ((obj.getLeft() + obj.getWidth()) / 2) - ((options.target.getLeft() + options.target.getWidth()) / 2);
var distY = ((obj.getTop() + obj.getHeight()) / 2) - ((options.target.getTop() + options.target.getHeight()) / 2);
// Set new position
findNewPos(distX, distY, options.target, obj);
}
// Snap objects to each other horizontally
// If bottom points are on same Y axis
if(Math.abs((options.target.getTop() + options.target.getHeight()) - (obj.getTop() + obj.getHeight())) < snap) {
// Snap target BL to object BR
if(Math.abs(options.target.getLeft() - (obj.getLeft() + obj.getWidth())) < snap) {
options.target.setLeft(obj.getLeft() + obj.getWidth());
options.target.setTop(obj.getTop() + obj.getHeight() - options.target.getHeight());
}
// Snap target BR to object BL
if(Math.abs((options.target.getLeft() + options.target.getWidth()) - obj.getLeft()) < snap) {
options.target.setLeft(obj.getLeft() - options.target.getWidth());
options.target.setTop(obj.getTop() + obj.getHeight() - options.target.getHeight());
}
}
// If top points are on same Y axis
if(Math.abs(options.target.getTop() - obj.getTop()) < snap) {
// Snap target TL to object TR
if(Math.abs(options.target.getLeft() - (obj.getLeft() + obj.getWidth())) < snap) {
options.target.setLeft(obj.getLeft() + obj.getWidth());
options.target.setTop(obj.getTop());
}
// Snap target TR to object TL
if(Math.abs((options.target.getLeft() + options.target.getWidth()) - obj.getLeft()) < snap) {
options.target.setLeft(obj.getLeft() - options.target.getWidth());
options.target.setTop(obj.getTop());
}
}
// Snap objects to each other vertically
// If right points are on same X axis
if(Math.abs((options.target.getLeft() + options.target.getWidth()) - (obj.getLeft() + obj.getWidth())) < snap) {
// Snap target TR to object BR
if(Math.abs(options.target.getTop() - (obj.getTop() + obj.getHeight())) < snap) {
options.target.setLeft(obj.getLeft() + obj.getWidth() - options.target.getWidth());
options.target.setTop(obj.getTop() + obj.getHeight());
}
// Snap target BR to object TR
if(Math.abs((options.target.getTop() + options.target.getHeight()) - obj.getTop()) < snap) {
options.target.setLeft(obj.getLeft() + obj.getWidth() - options.target.getWidth());
options.target.setTop(obj.getTop() - options.target.getHeight());
}
}
// If left points are on same X axis
if(Math.abs(options.target.getLeft() - obj.getLeft()) < snap) {
// Snap target TL to object BL
if(Math.abs(options.target.getTop() - (obj.getTop() + obj.getHeight())) < snap) {
options.target.setLeft(obj.getLeft());
options.target.setTop(obj.getTop() + obj.getHeight());
}
// Snap target BL to object TL
if(Math.abs((options.target.getTop() + options.target.getHeight()) - obj.getTop()) < snap) {
options.target.setLeft(obj.getLeft());
options.target.setTop(obj.getTop() - options.target.getHeight());
}
}
});
options.target.setCoords();
// If objects still overlap
var outerAreaLeft = null,
outerAreaTop = null,
outerAreaRight = null,
outerAreaBottom = null;
canvas.forEachObject(function (obj) {
if (obj === options.target) return;
if (options.target.isContainedWithinObject(obj) || options.target.intersectsWithObject(obj) || obj.isContainedWithinObject(options.target)) {
var intersectLeft = null,
intersectTop = null,
intersectWidth = null,
intersectHeight = null,
intersectSize = null,
targetLeft = options.target.getLeft(),
targetRight = targetLeft + options.target.getWidth(),
targetTop = options.target.getTop(),
targetBottom = targetTop + options.target.getHeight(),
objectLeft = obj.getLeft(),
objectRight = objectLeft + obj.getWidth(),
objectTop = obj.getTop(),
objectBottom = objectTop + obj.getHeight();
// Find intersect information for X axis
if(targetLeft >= objectLeft && targetLeft <= objectRight) {
intersectLeft = targetLeft;
intersectWidth = obj.getWidth() - (intersectLeft - objectLeft);
} else if(objectLeft >= targetLeft && objectLeft <= targetRight) {
intersectLeft = objectLeft;
intersectWidth = options.target.getWidth() - (intersectLeft - targetLeft);
}
// Find intersect information for Y axis
if(targetTop >= objectTop && targetTop <= objectBottom) {
intersectTop = targetTop;
intersectHeight = obj.getHeight() - (intersectTop - objectTop);
} else if(objectTop >= targetTop && objectTop <= targetBottom) {
intersectTop = objectTop;
intersectHeight = options.target.getHeight() - (intersectTop - targetTop);
}
// Find intersect size (this will be 0 if objects are touching but not overlapping)
if(intersectWidth > 0 && intersectHeight > 0) {
intersectSize = intersectWidth * intersectHeight;
}
// Set outer snapping area
if(obj.getLeft() < outerAreaLeft || outerAreaLeft == null) {
outerAreaLeft = obj.getLeft();
}
if(obj.getTop() < outerAreaTop || outerAreaTop == null) {
outerAreaTop = obj.getTop();
}
if((obj.getLeft() + obj.getWidth()) > outerAreaRight || outerAreaRight == null) {
outerAreaRight = obj.getLeft() + obj.getWidth();
}
if((obj.getTop() + obj.getHeight()) > outerAreaBottom || outerAreaBottom == null) {
outerAreaBottom = obj.getTop() + obj.getHeight();
}
// If objects are intersecting, reposition outside all shapes which touch
if(intersectSize) {
var distX = (outerAreaRight / 2) - ((options.target.getLeft() + options.target.getWidth()) / 2);
var distY = (outerAreaBottom / 2) - ((options.target.getTop() + options.target.getHeight()) / 2);
// Set new position
findNewPos(distX, distY, options.target, obj);
}
}
});
});
I based this fiddle off #Anna Phillips' and #gco's examples. It includes:
Corner snapping
Edge snapping
Objects can overlap
Objects are fully contained within the canvas
Objects cannot have a size larger than the canvas area
Here is the code:
window.canvas = new fabric.Canvas('fabriccanvas');
window.counter = 0;
var newleft = 0,
edgedetection = 20, //pixels to snap
canvasWidth = document.getElementById('fabriccanvas').width,
canvasHeight = document.getElementById('fabriccanvas').height;
canvas.selection = false;
plusrect();
plusrect();
plusrect();
function plusrect(top, left, width, height, fill) {
window.canvas.add(new fabric.Rect({
top: 300,
name: 'rectangle ' + window.counter,
left: 0 + newleft,
width: 100,
height: 100,
fill: 'rgba(' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ', 0.75)',
lockRotation: true,
originX: 'left',
originY: 'top',
cornerSize: 15,
hasRotatingPoint: false,
perPixelTargetFind: true,
minScaleLimit: 1,
maxHeight: document.getElementById("fabriccanvas").height,
maxWidth: document.getElementById("fabriccanvas").width,
}));
window.counter++;
newleft += 200;
}
this.canvas.on('object:moving', function (e) {
var obj = e.target;
obj.setCoords(); //Sets corner position coordinates based on current angle, width and height
if(obj.getLeft() < edgedetection) {
obj.setLeft(0);
}
if(obj.getTop() < edgedetection) {
obj.setTop(0);
}
if((obj.getWidth() + obj.getLeft()) > (canvasWidth - edgedetection)) {
obj.setLeft(canvasWidth - obj.getWidth());
}
if((obj.getHeight() + obj.getTop()) > (canvasHeight - edgedetection)) {
obj.setTop(canvasHeight - obj.getHeight());
}
canvas.forEachObject(function (targ) {
activeObject = canvas.getActiveObject();
if (targ === activeObject) return;
if (Math.abs(activeObject.oCoords.tr.x - targ.oCoords.tl.x) < edgedetection) {
activeObject.left = targ.left - activeObject.currentWidth;
}
if (Math.abs(activeObject.oCoords.tl.x - targ.oCoords.tr.x) < edgedetection) {
activeObject.left = targ.left + targ.currentWidth;
}
if (Math.abs(activeObject.oCoords.br.y - targ.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top - activeObject.currentHeight;
}
if (Math.abs(targ.oCoords.br.y - activeObject.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top + targ.currentHeight;
}
if (activeObject.intersectsWithObject(targ) && targ.intersectsWithObject(activeObject)) {
targ.strokeWidth = 10;
targ.stroke = 'red';
} else {
targ.strokeWidth = 0;
targ.stroke = false;
}
if (!activeObject.intersectsWithObject(targ)) {
activeObject.strokeWidth = 0;
activeObject.stroke = false;
}
});
});
What I'd like to know is if it's possible to extend this to add the following features:
Dynamic snapping. Continuing to drag an object after the initial snap will temporarily disable snapping until the object stops moving. For example, if I drag one box next to another, they will snap together once they are within range. However if I continue moving the first box, I can "drop" it in a position where it is within the snapping range but not aligned to the other box.
Show guide lines when selected object is within range of another object. Currently we add a border around the target object, but it would be better to show guidelines that extend outwards (possibly to the edge of the canvas) to more easily visualize the bounds of the target object.
Parallel snapping. When moving an object that is already snapped to the target object, the selected object should snap to the target object in such a way that the tops, bottoms, or sides of both objects are parallel. For example, assume that the selected square is snapped to the left of the target square and that the top of the selected square is below the top of the target square. Moving the selected square up should cause its top to snap into alignment with the top of the target once in range. The same logic should apply when moving it down, or if the selected object is above/below the target and being moves horizontally.
I needed snapping of unequal sized areas. jsfiddle
var canvas = new fabric.Canvas('c');
canvas.setDimensions({width:window.innerWidth});
var edge_detection_external = 21;
var corner_detection = 5;
canvas.selection = false;
canvas.on('object:moving', function (e) {
var obj = e.target;
obj.setCoords();
function update_position(obj){
return function(targ){
if(targ === obj) return;
// Check overlap case https://www.geeksforgeeks.org/find-two-rectangles-overlap/
if(!(function(targ,obj){
if(obj.aCoords.tl.x > targ.aCoords.br.x || targ.aCoords.tl.x > obj.aCoords.br.x)
return false;
if(targ.aCoords.tl.y > obj.aCoords.br.y || obj.aCoords.tl.y > targ.aCoords.br.y)
return false;
return true;
})(targ,obj)){
// is on RIGHT or LEFT?
if((obj.top > targ.top && obj.top < targ.top + targ.height)
|| (targ.top > obj.top && targ.top < obj.top + obj.height)){
// Object is to the RIGHT and Edge detection
if(obj.aCoords.tl.x > targ.aCoords.br.x
&& obj.aCoords.tl.x - targ.aCoords.br.x < edge_detection_external){
obj.set({left:targ.aCoords.br.x});
// Corner detection
obj.setCoords();
if(Math.abs(targ.aCoords.tr.y - obj.aCoords.tl.y) < corner_detection)
obj.set({top:targ.top});
else if(Math.abs(targ.aCoords.br.y - obj.aCoords.bl.y) < corner_detection)
obj.set({top:targ.top + targ.height - obj.height});
}
// LEFT
if(targ.aCoords.tl.x > obj.aCoords.br.x
&& targ.aCoords.tl.x - obj.aCoords.br.x < edge_detection_external){
obj.set({left:targ.aCoords.tl.x - obj.width});
obj.setCoords();
if(Math.abs(targ.aCoords.tl.y - obj.aCoords.tr.y) < corner_detection)
obj.set({top:targ.top});
else if(Math.abs(targ.aCoords.bl.y - obj.aCoords.br.y) < corner_detection)
obj.set({top:targ.top + targ.height - obj.height});
}
}
// is on TOP or BOTTOM?
if((obj.left > targ.left && obj.left < targ.left + targ.width)
|| (targ.left > obj.left && targ.left < obj.left + obj.width)){
// TOP
if(targ.aCoords.tl.y > obj.aCoords.br.y
&& targ.aCoords.tl.y - obj.aCoords.br.y < edge_detection_external){
obj.set({top:targ.aCoords.tl.y - obj.height});
obj.setCoords();
if(Math.abs(targ.aCoords.tl.x - obj.aCoords.bl.x) < corner_detection)
obj.set({left:targ.left});
else if(Math.abs(targ.aCoords.tr.x - obj.aCoords.br.x) < corner_detection)
obj.set({left:targ.left + targ.width - obj.width});
}
// BOTTOM
if(obj.aCoords.tl.y > targ.aCoords.br.y
&& obj.aCoords.tl.y - targ.aCoords.br.y < edge_detection_external){
obj.set({top:targ.aCoords.br.y});
obj.setCoords();
if(Math.abs(targ.aCoords.bl.x - obj.aCoords.tl.x) < corner_detection)
obj.set({left:targ.left});
else if(Math.abs(targ.aCoords.br.x - obj.aCoords.tr.x) < corner_detection)
obj.set({left:targ.left + targ.width - obj.width});
}
}
}
}
}
canvas.getObjects('group').some(update_position(obj));
});
String.prototype.to_inches = function(){
return this.split('-').map(function(value,index){
value = Number(value);
if(index == 0)
return value * 12
else
return value
}).reduce(function(total,current){
return total + current;
});
}
Array.prototype.to_object_list = function(){
const preserved = [...this];
var header = this.splice(0,1)[0];
for(var i = 0;i < this.length; i++){
var obj = {};
for(var j = 0;j < header.length; j++){
obj[header[j].toLowerCase()] = this[i][j];
}
this[i] = obj;
}
return preserved;
}
function draw_areas(){
var offset = 0;
return function(area_params,index){
if(area_params.area.indexOf('>') === -1){
var area = new fabric.Rect({
fill: 'red',
width:area_params.width.to_inches(),
height:area_params.length.to_inches(),
});
var text = new fabric.Text(area_params.area + '\n' + area_params.width + ' x ' + area_params.length,{
fontSize:12,
fill:"white"
});
if(text.width - area.width > 0){
text.set('width',area.width);
}
if(text.height - area.height > 0){
text.set('height',area.height);
}
var group_name = 'group_' + area_params.area.split(' ').join('-');
var group = new fabric.Group([area,text],{
name: group_name,
left: 5,
top: 5*(index++) + offset,
});
canvas.add(group);
offset = area_params.length.to_inches() + offset;
canvas.setDimensions({height:5*(index++) + offset});
}
}
}
function handler_get_data(data){
data = JSON.parse(data);
data.to_object_list();
data.forEach(draw_areas());
}
var d = '[["Area","Width","Length"],["Bedroom 1","19-5.5","14"],["Kitchen","14","16-3"],["Bedroom 2","13-6","12-9"]]';
handler_get_data(d);