Detecting sibling divs enclosed by position - javascript

I have been asked to write a prototype application where a user lassos important locations on a background image inside a div frame. My approach is Photoshop-like, drawing divs of dynamic size and position into the DOM within the frame. But, my next step is where I need help.
I need to allow the user to select groups of lasso divs. These will be used by another function. The "tailingdiv" is a plain 1px border div that will be mouse drawn to enclose some of its sibling divs. The challenge is detecting which divs are positioned inside the "tailingdiv"! In other words, I may need to compare the xy coordinates of these divs and determine which ones are visibly inside the tailingdiv in spite of it being sibling in the DOM tree. As a bonus, I would like to work in some fudge factor for cases where a div is 75% inside the tailingdiv.
<div class="frame" id="lassoFrame" style="display: block; height: 333px; width: 500px; background-image: url("dump-1459285968.png"); background-size: 500px 333px;">
<div class="lasso ui-draggable" style="position: absolute; left: 96px; top: 263px; width: 320px; height: 35px;" coords="{"x1":96,"x2":416,"y1":263,"y2":298}"></div>
<div class="lasso ui-draggable" style="position: absolute; left: 62px; top: 8px; width: 89px; height: 46px;" coords="{"x1":62,"x2":151,"y1":8,"y2":54}"></div>
<div class="nudgeControl lasso ui-draggable" style="position: absolute; left: 161px; top: 14px; width: 88px; height: 40px;" coords="{"x1":161,"x2":249,"y1":14,"y2":54}"></div>
<div class="tailingdiv" style="position: absolute; left: 51px; top: 4px; width: 388px; height: 71px;" coords="{"x1":162,"x2":249,"y1":13,"y2":56}"></div>
</div>
It may look something like this. We have a background where someone has mouse drawn a set of lassos and they want to draw the tailingdiv to enclose the top two lasso divs. Upon drawing the tailingdiv (mouseup event), I need to examine the coordinates of these objects and determine which divs are visually positioned inside the tailingdiv.

Here is an answer that assumes your want to select all divs with class lasso that touch or collide with the div with class tailingdiv:
function getBox(elem) {
elem=$(elem);
var ofs=elem.offset();
return {
x:ofs.left,
y:ofs.top,
width:elem.width(),
height:elem.height()
};
}
function collide(elem1, elem2) {
var a=getBox(elem1);
var b=getBox(elem2);
return (Math.abs(a.x - b.x) * 2 < (a.width + b.width)) &&
(Math.abs(a.y - b.y) * 2 < (a.height + b.height));
}
$(function() {
var tail=$('.tailingdiv');
var divs=$('div.lasso').filter(function() {
return collide(tail,this);
});
console.log(divs);
});
Since you need the entire box, you might consider using getBoundingClientRect() instead of the classic jQuery functions.
Here is the code for using getBoundingClientRect and also specifying a minimal overlap percentage (value between 0 and 1. If omitted it will default to 0.7. Set it to 0 to use it as in the previous example - strict collision):
function getBox(elem) {
var e=$(elem);
if (e.length==0||e.length>1) throw 'getBox accepts only one element as argument';
var rect=e[0].getBoundingClientRect();
return {
x:rect.left,
y:rect.top,
width:rect.width,
height:rect.height
};
}
function collide(elem1, elem2, overlap) {
if (typeof(overlap)=='undefined') overlap=0.7;
var a=getBox(elem1);
var b=getBox(elem2);
var area=Math.max(0, Math.min(a.x+a.width, b.x+b.width) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y+a.height, b.y+b.height) - Math.max(a.y, b.y))
return area/(b.width*b.height)>overlap;
}
And here is an example of jQuery integration and use:
$.fn.overlappedBy=function(elem,overlap) {
return this.filter(function() {
return collide(elem,this,overlap);
});
}
$(function() {
var tail=$('.tailingdiv');
var divs=$('div.lasso').overlappedBy('.tailingdiv',0.3);
console.log(divs);
});

Related

JS: Get an element's visible area coordinates

I need a function that can calculate the visible area of ​​an element that is currently visible on the screen without the part hidden by overflow: scroll, position: absolute etc.
That is, the result of this function getVisiblePart(el) will be Visible Rect is: {x: 10, y: 20, height: 50, width: 700}
Background of the question:
The need for such a function is due to a feature of the W3C specification for working with webdriver: https://w3c.github.io/webdriver/webdriver-spec.html#element-interactability
An element’s in-view centre point is the centre point of the area of the first DOM client rectangle that is inside the viewport.
Frameworks like selenoid/selenide for e2e tests use the w3c principle to calculate the center of the visible element to move the cursor to it, while allowing you to specify an offset. The main problem is to find out the actual size of the visible area of ​​the element at the moment, in order to calculate the correct offsets, for example, to calculate the upper left border.
The solution to this problem for selenoid/selenide would be:
Selenide.actions().moveToElement(el, getVisiblePart(el).width / -2, getVisiblePart(el).height / -2)
I have read a lot of similar topics, for example:
How can I tell if a DOM element is visible in the current viewport? (it only answers the question whether the element has been seen or not)
How to check if element is visible after scrolling? (same)
Is it possible to programmatically determine whether W3C action commands are used? (close but no working answer)
All answers either give a boolean whether the element is visible, or fail to take into account that the element may be partially hidden overflow: scroll
Real Example with scrolls (I need to find visible blue rect position with any scroll position):
.a {
height: 250px;
overflow: scroll;
padding-top: 100px;
background: yellow;
}
.b {
height: 500px;
overflow: scroll;
}
.c {
height: 1000px;
background: blue;
}
#target {
border: 2px dashed red;
position: absolute;
pointer-events: none;
transform: translate(-1px,-1px); /*because of border*/
}
<div class="a">
<div class="b">
<div class="c" />
</div>
</div>
<div id="target" />
In answer to this question, I have already partially solved this problem, achieved the result I needed using Intersection Observer API, but I do not consider this solution to be good, at least because it is not synchronous with when the function is called and on the issue of cross-browser compatibility.
My workaround based on Intersection Observer API.
I do not consider this solution to be good, at least because it is not synchronous with when calling the function and on the issue of cross-browser compatibility.
This example perfectly demonstrates the final version that I want to get.
var options = {
// root: document.querySelector('#scrollArea'),
// rootMargin: '0px',
threshold: 1
}
var callback = function(entries, observer) {
const r = entries[0].intersectionRect;
document.getElementById('target').setAttribute('style', `top: ${r.top}px;left: ${r.left}px;height: ${r.height}px;width: ${r.width}px;`);
observer.unobserve(c);
};
const c = document.querySelector('.c');
setInterval(() => {
var observer = new IntersectionObserver(callback, options);
observer.observe(c);
}, 200);
.a {
height: 250px;
overflow: scroll;
padding-top: 100px;
background: yellow;
}
.b {
height: 500px;
overflow: scroll;
}
.c {
height: 1000px;
background: blue;
}
#target {
border: 2px dashed red;
position: absolute;
pointer-events: none;
transform: translate(-1px,-1px); /*because of border*/
}
<div class="a">
<div class="b">
<div class="c" />
</div>
</div>
<div id="target" />
Explanation: every 200ms we calculate the visible area of ​​the blue element and highlight it.
An example of how I use this for Selenide:
val containerSelector = "div.testEl"
val rect = executeAsyncJavaScript<String>(
"var callback = arguments[arguments.length - 1];" +
"var el = document.querySelector('" + containerSelector + "');" +
"new IntersectionObserver(" +
" (entries, observer) => {" +
" observer.unobserve(el);" +
" var r = entries[0].intersectionRect;" +
" callback([r.x, r.y, r.height, r.width].map(v => Math.ceil(v)).join(','));" +
" }," +
" { threshold: 1 }" +
").observe(el);"
)!!.split(',').toTypedArray();
LEFT_TOP_X = rect[3].toInt() / -2 + 1 // +1 for Float tolerance
LEFT_TOP_Y = rect[2].toInt() / -2 + 1 // +1 for Float tolerance
// Move cursor to TOP LEFT
Selenide.actions().moveToElement(el, LEFT_TOP_X, LEFT_TOP_Y)
v2 may help but the asynch still stinks
https://github.com/szager-chromium/IntersectionObserver/blob/v2/explainer.md
https://w3c.github.io/IntersectionObserver/v2/
https://chromestatus.com/feature/5878481493688320
https://szager-chromium.github.io/IntersectionObserver/demo/cashbomb/hidden/

Css - how to change element size if it reached to certain spot on screen?

I've dynamically created some divs with random color who are scattered over the page randomly. I want to use css to give them a condition that says that if the div is located above let's say 600px on the screen - his size will change. I know the "if" statement for css is #media but I didn't figure how to use it right in this situation. Can you help me?
Example of a div (they all have the same class - "frog")
<div id="frog" class="fas fa-ad" style="width: 66px; height: 66px;
background-color: rgb(87, 58, 55); position: absolute; left: 312px; top:
93px; display: block;"></div>
You can't do that with CSS only. The only way you can have dynamic styling based on what happens in the windows in CSS is to use media queries. However, the docs precise that you can only detect window-level data, like the device width, or whether the page is displayed on a screen or on a printed paper, etc.
You'll have to change your style with JS. This is often a bad way to have dynamic styling, because the only way to do so is to 'probe' the DOM (using for example setInterval). Luckily your case is an exception - since you update your divs position with JS, you can check directly after that the position of your divs, and update your styles accordingly!
Example using the very useful getBoundingClientRect:
// select the frog element
let frog = document.getElementById('frog');
let count = 0;
setInterval(() => {
// update the frog position
frog.style.top = `${count}px`;
count = (count+2)%200;
// check if the frog is after 100px from the top of the window
if (frog.getBoundingClientRect().top>100) {
frog.className = 'over-100';
} else {
frog.className = '';
}
}, 1000/30);
#frog {
position: fixed;
top: 0;
left: 0;
width: 50px;
height: 50px;
background-color: green;
}
#frog.over-100 {
background-color: olive;
}
<div id="frog"></div>

How do you get the new top and left of a child div inside a rotated parent?

I am trying to use the new top and left of the child div inside a rotated parent to limit the draggable area of the rotated parent. Im using jquery draggable for this.
EDIT :
Here is the jsfiddle . Im planning to use the red dot on the rotated div to use as marker to check if it collided with the boundaries of the container. I need to get the new position(top and left) of that red marker to make use of my ready made function to contain the draggable.
In order to calculate the top or left offset for any element, you need to use .getBoundingClientRect(), in addition to accounting for the window scroll.
This is also the case for rotated elements, as can be seen in the following example:
function findTopLeft(element) {
var rec = document.getElementById(element).getBoundingClientRect();
return {
top: rec.top + window.scrollY,
left: rec.left + window.scrollX
};
}
console.log(findTopLeft('inner'));
#outer {
position: absolute;
top: 25%;
left: 25%;
width: 100px;
height: 50px;
border: 1px solid black;
transform: rotate(90deg);
}
<div id="outer">
<div id="inner">Text</div>
</div>
Hope this helps! :)

Alternative to absolute positioning that updates parents height

I am working on a library that draws brackets.
I am calculating the coordinates of each element (matches and lines) using javascript. Each element has position: absolute; and uses left and top to set their x and y coordinates.
Because I am using position: absolute; it is necessary to set the parent to position: relative; so that the children are positioned relative to their parent.
This was working perfectly until I noticed that the parents height was not updating from its children, because position: absolute; elements are taken out of the flow of the document.
I need the parent to have height so that I can place other elements underneath, and give the parent styles such as background-color...
Is there an alternative to absolute positioning that uses x and y coordinates but also keeps them in the flow of the document to allow the parents width and height to adjust automatically?
Or, if that is not possible, is there away using vanilla javascript (no jQuery or other libraries) to find out the width and height of the contents of the parent div. If this is possible I can just set the parent's width and height styles through javascript.
What I've tried so far
I tried to set the children to position: relative; instead of position: absolute; which I believe would work if you only have one child. However, with more than one child, the children are not relative to the parent but are now relative to the previous child which messes things up.
Even though the parent has no height there is still a vertical scrollbar on the page. Using javascript, I tried to get the scrollHeight and height of elements such as document, document.body and window. This did not work because either the result was undefined or the result was incorrect.
Right now my temporary solution is to set body height to 2500px which should be the highest it will ever need to be. The problem with this is that there will always be a scrollbar, most of the time scrolling to nothing.
Code
<div class="BrackChart_wrapper">
<div class="BrackChart_match"> ... </div>
<div class="BrackChart_line"></div>
etc.
</div>
.BrackChart_wrapper {
position: relative;
top: 0px;
left: 0px;
}
.BrackChart_match, .BrackChart_line {
position: absolute;
}
Thank you for the help, much appreciated!
JSFiddle: https://jsfiddle.net/jmjcocq8/1/
Solution: https://jsfiddle.net/jmjcocq8/2/
I'm fairly sure there's no alternative that both lets you position the children absolutely (relative to their parent) and keeps them in the flow such that they contribute to the size of the parent.
But since you're calculating the positions of the elements yourself, and you know how tall they are, you can set the height of the parent based on the lowest child's y plus height.
You haven't shown your code, but for instance:
var forEach = Array.prototype.forEach;
function positionChildren(parent) {
var left = 0;
var top = 0;
forEach.call(parent.children, function(child, index) {
child.style.left = left + "px";
child.style.top = top + "px";
left += 20;
top += 10;
});
var height = top - 10 + 1 + parent.lastElementChild.clientHeight;
console.log("height = " + height);
parent.style.height = height + "px";
}
positionChildren(document.getElementById("parent"));
#parent {
border: 1px solid black;
background-color: #ddd;
position: relative;
}
.child {
position: absolute;
border: 1px solid blue;
padding: 0;
margin: 0;
}
<div id="parent">
<div class="child">Child1</div>
<div class="child">Child2</div>
<div class="child">Child3</div>
</div>

Two overlapping images via CSS and JS

I am trying to enable a user to drag an image (e.g. a face) on another image (e.g. a map square). I implemented the drag&drop with an Angular directive and it kinda work. What is not working is the position of the dropped image (the face): it is not overlaying, but it is being placed below the map square.
The starting HTML code is generated via ng-repeat, but the resulting element is this:
<span style="display: inline-block;position:relative;">
<img src="map_square.jpg" class="map-image">
</span>
When dropping, it becomes:
<span style="display: inline-block">
<img src="map_square.jpg" class="map-image">
<img src="face.jpg" class="face-image-on-map">
</span>
This is my CSS code:
.map-image {
position: relative;
max-width: 42px;
z-index: 0;
}
.face-image-on-map {
position: absolute;
width: 100%;
max-width: 42px;
z-index: 100;
opacity: .8;
}
As a result of this, I would expect the face image to be over the map square, both being inside the span-delimited area (42*42px).
Instead, the face image is outside the span area, below the map square (actually, it is over another map square image, i.e. the one below the actual target).
Changing position of the face causes the face image to be placed on the right of the target (far away from it).
How can I fix this?
I think you're missing the top and the left property in .face-image-on-map.
So I would recommend this:
.face-image-on-map {
position: absolute;
top: 0; /* Positoning: distance from top border */
left: 0; /* Positioning: distance from left border */
width: 100%;
max-width: 42px;
z-index: 100;
opacity: .8;
}
(See the W3Schools page for more information)
Hope this is want you wanted :)

Categories

Resources