Can you tell if one element is touching another using JavaScript? - javascript

I want to use JavaScript to check if one div element (that can be dragged) is touching another div element.
Here is some code:
<div id="draggable" style="position: absolute; top: 100px; left: 200px; background-color: red; width: 100px; height: 100px;"></div>
<div style="background-color: green; width: 100px; height: 100px;"></div>
Can this be done?
If so, how?
Edit: I do not want to use jQuery, just plain old JavaScript!

A plain old JS solution
Below is a "plain old JavaScript" rewrite of the overlap detection function found in this answer to the question titled "jQuery/Javascript collision detection".
The only real difference between the two is the replacement of the use of jQuery to get element position and width for calculating the bounding box.
Native JavaScript makes this task easy via the Element.getBoundingClientRect() method, which returns the four values needed to create the position matrix returned by the getPositions function.
I added a click handler for the boxes as a simple demonstration of how you might use the function to compare a target (clicked, dragged, etc.) element to a set of selected elements.
var boxes = document.querySelectorAll('.box');
boxes.forEach(function (el) {
if (el.addEventListener) {
el.addEventListener('click', clickHandler);
} else {
el.attachEvent('onclick', clickHandler);
}
})
var detectOverlap = (function () {
function getPositions(elem) {
var pos = elem.getBoundingClientRect();
return [[pos.left, pos.right], [pos.top, pos.bottom]];
}
function comparePositions(p1, p2) {
var r1, r2;
if (p1[0] < p2[0]) {
r1 = p1;
r2 = p2;
} else {
r1 = p2;
r2 = p1;
}
return r1[1] > r2[0] || r1[0] === r2[0];
}
return function (a, b) {
var pos1 = getPositions(a),
pos2 = getPositions(b);
return comparePositions(pos1[0], pos2[0]) && comparePositions(pos1[1], pos2[1]);
};
})();
function clickHandler(e) {
var elem = e.target,
elems = document.querySelectorAll('.box'),
elemList = Array.prototype.slice.call(elems),
within = elemList.indexOf(elem),
touching = [];
if (within !== -1) {
elemList.splice(within, 1);
}
for (var i = 0; i < elemList.length; i++) {
if (detectOverlap(elem, elemList[i])) {
touching.push(elemList[i].id);
}
}
if (touching.length) {
console.log(elem.id + ' touches ' + touching.join(' and ') + '.');
alert(elem.id + ' touches ' + touching.join(' and ') + '.');
} else {
console.log(elem.id + ' touches nothing.');
alert(elem.id + ' touches nothing.');
}
}
#box1 {
background-color: LightSeaGreen;
}
#box2 {
top: 25px;
left: -25px;
background-color: SandyBrown;
}
#box3 {
left: -50px;
background-color: SkyBlue;
}
#box4 {
background-color: SlateGray;
}
.box {
position: relative;
display: inline-block;
width: 100px;
height: 100px;
color: White;
font: bold 72px sans-serif;
line-height: 100px;
text-align: center;
cursor: pointer;
}
.box:hover {
color: Black;
}
<p>Click a box to see which other boxes are detected as touching it.<br />
<em>If no alert dialog box appears, open your console to view messages.</em></p>
<div class="box" id="box1">1</div>
<div class="box" id="box2">2</div>
<div class="box" id="box3">3</div>
<div class="box" id="box4">4</div>

Update: I realize now that this only accounts for the intersection of the top left corner of the target element and therefore, doesn't provide a complete solution. But I'll leave it up for posterity in case someone finds it useful for other purposes.
Use element.getBoundingClientRect() and document.elementFromPoint()
You can use element.getClientBoundingRect() (src) to get the position of the target (clicked, dragged, etc.) element.
Temporarily hide the target element, then use document.elementFromPoint(x, y) (src) to get the top-most element at that position, and then check it's class name for comparison (you could compare any attribute or property instead, if you prefer).
To achieve cross-browser compatible behavior from this method
read: document.elementFromPoint – a jQuery solution (You don't
have to use jQuery to achieve this result. The method can be
replicated in pure JS.)
Addendum:
I am only showing the function for detecting overlap instead of showing drag-and-drop or drag-to-move functionality because it isn't clear which of those, if either, you are trying to implement and there are other answers showing how to accomplish various drag patterns.
In any case, you can use the detectCollision() function below in combination with any drag solution.
var box2 = document.getElementById('box2'),
box3 = document.getElementById('box3');
box2.onclick = detectCollision;
box3.onclick = detectCollision;
function detectCollision(e) {
var elem = e.target,
elemOffset = elem.getBoundingClientRect(),
elemDisplay = elem.style.display;
// Temporarily hide element
elem.style.display = 'none';
// Check for top-most element at position
var topElem = document.elementFromPoint(elemOffset.left, elemOffset.top);
// Reset element's initial display value.
elem.style.display = elemDisplay;
// If a top-most element is another box
if (topElem.className.match(/box/)) {
alert(elem.id + " is touching " + topElem.id);
} else {
alert(elem.id + " isn't touching another box.");
};
}
#box1 {
background-color: LightSeaGreen;
}
#box2 {
top: 25px;
left: -25px;
background-color: SandyBrown;
}
#box3 {
background-color: SkyBlue;
}
.box {
position: relative;
display: inline-block;
width: 100px;
height: 100px;
}
.clickable {
cursor: pointer;
}
<div class="box" id="box1"></div>
<div class="box clickable" id="box2"></div>
<div class="box clickable" id="box3"></div>

Related

how to make a div grow to cover a paragraph in javascript with transition?

I am trying to make a transition with a div that should grow and overlap a text.
Here are my codes
const box = document.querySelector("#box");
const mybutt = document.querySelector("#mybutt");
mybutt.addEventListener("click", transitionfunction);
function transitionfunction() {
if(box.style.height != "100px"){
box.style.height = "100px";
box.style.transition = "2s";
}
else {
box.style.height = "50px";
box.style.transition = "2s";
}
}
#box {
background: red;
height: 50px;
width: 50px;
}
#para {
postion: fixed;
}
<div id="parentdiv">
<div id="box"></div>
<p id="para">Help</p>
</div>
<button id="mybutt">click</button>
At the moment, on the click of the button, both the button and the paragraph para move down, I want them to be fixed and I want the div, #box to cover the para but its not working. I tried putting it to fixed but doesnt work. And on the click on the button again, it should reveal the text again.
If you use position: fixed;, you should manually set the top property.
To make a div overlay some text, use z-index
const box = document.querySelector("#box");
const mybutt = document.querySelector("#mybutt");
mybutt.addEventListener("click", transitionfunction);
function transitionfunction() {
if (box.style.height != "100px"){
box.style.height = "100px";
box.style.transition = "2s";
} else {
box.style.height = "50px";
box.style.transition = "2s";
}
}
#mybutt {
position: fixed;
top: 120px;
}
#box {
background: red;
position: fixed;
height: 50px;
width: 50px;
z-index: 2;
}
#para {
position: fixed;
z-index: 1;
top: 60px;
}
<div id="parentdiv">
<div id="box"></div>
<p id="para">Help</p>
</div>
<button id="mybutt">click</button>
Firstly, you spelled "position" wrong for #para. Change it to:
#para {
position: absolute;
top: 10%;
}
This will keep the paragraph positioned in one spot; it won't move.
Fixed will work, although you might want to use 'absolute' instead if you want it to anchored to it's parent instead of the window itself.
Also, 'position' is misspelled; not sure if it is in your testing code.
The 'top' property has to be set for the element to know where to anchor itself, the 'position' property is what to anchor to.
HTML
<div id="parentdiv">
<div id="box"></div>
<p id="para">Help</p>
</div>
</div>
<button id="mybutt">click</button>
CSS
<style>
#box {
background: red;
height: 50px;
width: 50px;
}
#para {
position: absolute;
top:70;
}
</style>
*You also might want to move '#para' outside '#parentdiv', but it depends what you'll trying to ultimately do, it does work inside too.
Added:
To include an alert at 75px, you have to use a function that gives you more granular control(as far as I know at least). This is one solution:
<script>
const box = document.querySelector("#box");
const mybutt = document.querySelector("#mybutt");
mybutt.addEventListener("click", transitionfunction);
var intHeight = $("#box").css("height").split("p")[0];
function transitionfunction() {
if(intHeight < 100) {
intHeight++;
$("#box").css("height", intHeight + "px");
if (intHeight===76)
alert("75px!")
requestAnimationFrame(transitionfunction);
}
intHeight = $("#box").css("height").split("p")[0];
mybutt.addEventListener("click", revtransitionfunction);
mybutt.removeEventListener("click", transitionfunction);
}
function revtransitionfunction() {
if(intHeight >= 50) {
intHeight--;
$("#box").css("height", intHeight + "px");
if (intHeight===74)
alert("75px!")
requestAnimationFrame(revtransitionfunction);
}
intHeight = $("#box").css("height").split("p")[0];
mybutt.addEventListener("click", transitionfunction);
mybutt.removeEventListener("click", revtransitionfunction);
}

How to check if there is any part of element on top using js?

I need to check for given element if there exists any point that elementFromPoint with this point given as argument will return this element. I do not need to find that point - just information if there is at least one. It is equivalent to "if there exists any part of the element which is not covered by another element".
Example: JSFiddle - since there is a part of num2 and num3 which is "on top" desired function should return true for those two. However for num1 result should be false.
I'm not sure if there's a better way than this brute force option of getting the element's boundaries using element.getClientRects(), and then iterating over that area with elementFromPoint to see if it ever returns that element:
var checkIfOnTop = function(el) {
console.log("Checking",el);
var rect = el.getClientRects()[0];
for (var x = rect.left; x <= rect.right; x++) {
for (var y = rect.top; y <= rect.bottom; y++) {
if (document.elementFromPoint(x, y) === el) {
console.log("This element is reachable by click at ",x,y);
return true;
}
}
}
console.log("This element is not reachable by click");
return false;
}
checkIfOnTop(document.getElementById('num1'));
checkIfOnTop(document.getElementById('num2'));
checkIfOnTop(document.getElementById('num3'));
#container { position: relative }
#num1 { position: absolute; left: 50px; height: 200px; width: 200px; background-color: blue}
#num2 { position: absolute; left: 50px; height: 200px; width: 200px; background-color: green}
#num3 { position: absolute; left: 100px; height: 200px; width: 200px; background-color: red}
<div id='container'>
<div id='num1'></div>
<div id='num2'></div>
<div id='num3'></div>
</div>
Not recommended for very large elements, of course.

A Function to Determine if LI is overflowing

JSFiddle: https://jsfiddle.net/1fLmtyoj/15/
I'm following this example to see if an element is overflowing, but something isn't working, this is for a DIV: Check with jquery if div has overflowing elements and
https://stackoverflow.com/a/7668692/1005607
I have an <LI> tag containing <SPAN>s. From the above answer, it's necessary to check (1) Overflow active, (2) Semi-visible parts. I combined them into the following function.
function isOverflowing(element) {
// 1. Main Part for overflow check
if (element.offsetHeight < element.scrollHeight ||
element.offsetWidth < element.scrollWidth) {
return true;
}
// 2. Partially invisible items
var invisibleItems = [];
for(var i=0; i<element.childElementCount; i++){
if (element.children[i].offsetTop + element.children[i].offsetHeight >
element.offsetTop + element.offsetHeight ||
element.children[i].offsetLeft + element.children[i].offsetWidth >
element.offsetLeft + element.offsetWidth ){
invisibleItems.push(element.children[i]);
}
}
if (invisibleItems.length) {
return true;
}
// Otherwise, neither Part 1 nor 2, return FALSE
return false;
}
In the JSFiddle, the expected output is that #1 and #3 are not overflowing, but #2 and #4 are overflowing. But all 4 are shown as not overflowing.
If you were to display element via the console, you would see that element does not have the attribute childElementCount -- but the first member of the element array does. Thus, refer to element[0] for all the attributes, and it behaves as expected. I think this is due to the selector you've used.
function isOverflowing(element) {
// 1. Main Part for overflow check
if (element.offsetHeight < element.scrollHeight ||
element.offsetWidth < element.scrollWidth) {
return true;
}
// 2. Partially invisible items
var invisibleItems = [];
for(var i=0; i<element[0].childElementCount; i++){
if (element[0].children[i].offsetTop + element[0].children[i].offsetHeight >
element[0].offsetTop + element[0].offsetHeight ||
element[0].children[i].offsetLeft + element[0].children[i].offsetWidth >
element[0].offsetLeft + element[0].offsetWidth ){
invisibleItems.push(element[0].children[i]);
}
}
if (invisibleItems.length) {
return true;
}
// Otherwise, neither Part 1 nor 2, return FALSE
return false;
}
$('#result').html('#1? ' + isOverflowing($('li:eq(0)')) +
'#2? ' + isOverflowing($('li:eq(1)')) +
'#3? ' + isOverflowing($('li:eq(2)')) +
'#4? ' + isOverflowing($('li:eq(3)'))
);
.type1 {
border: 1px solid black;
width: 130px;
height: 30px;
margin-bottom: 5px;
}
.type2 {
border: 1px dotted red;
width: 50px;
height: 25px;
margin-bottom: 45px;
}
.type3 {
border: 1px dashed blue;
width: 200px;
height: 40px;
margin-bottom: 5px;
}
.type4 {
border: 1px dashed green;
width: 100px;
height: 10px;
margin-bottom: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li class="type1"><span>Some text in LI 1</span></li>
<li class="type2"><span>Some more text in LI 2</span></li>
<li class="type3"><span>A long string of text in LI3</span></li>
<li class="type4"><span>A much longer string of text in LI4</span></li>
</ul>
<br/>
<br/>
<br/>
<br/>
<p id="result"></p>
As you say, however, this solution isn't generic enough -- it doesn't allow for nested DOM nodes. To fix this, we would want a recursive function. Try this, possibly. It's working, I think, but it really made my brain hurt to think recursively. ;)
function isOverflowing(element) {
/*******
* This is going to be a recursive function -- first,
* it'll check if there are children elements and,
* if not, simply return true. In the event there
* are child elements, it should recurse over each
* to see if any child overflows, whether partially
* invis or not.
******/
var elOverflows;
var el = element[0];
// On the first iteration, we initialize these. On every
// recursive iteration, we simply preserve them
var mainHeight = el.offsetTop + el.offsetHeight,
mainOffsetHeight = el.offsetHeight,
mainWidth = el.offsetLeft + el.offsetWidth,
mainOffsetWidth = el.offsetWidth;
// 1. Main Part for overflow check
if (mainOffsetHeight < el.scrollHeight ||
mainOffsetWidth < el.scrollWidth) {
elOverflows = true;
return elOverflows;
}
/***
* 2. If the current element doesn't contain any
* children, and the above check didn't return,
* then we don't have any overflow.
***/
if (el.childElementCount == 0) {
elOverflows = false;
return elOverflows;
} else {
// Here, we have child elements. We want to iterate
// over each of them and re-call isOverflowing() on
// each one. This is the recursion, allowing us to
// have any number of nested child elements.
$(el.children).each(function() {
elOverflows = isOverflowing($(this));
});
return elOverflows;
}
}
$("#container li").each(function() {
$("#result").append("<p>#" + $(this).index() + ": " + isOverflowing($(this)));
})
.type1 {
border: 1px solid black;
width: 130px;
height: 30px;
margin-bottom: 5px;
}
.type2 {
border: 1px dotted red;
width: 50px;
height: 25px;
margin-bottom: 45px;
}
.type3 {
border: 1px dashed blue;
width: 200px;
height: 40px;
margin-bottom: 5px;
}
.type4 {
border: 1px dashed green;
width: 100px;
height: 10px;
margin-bottom: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id='container'>
<li class="type1"><span>Some text in LI 1</span></li>
<li class="type2"><span>Some more text in LI 2</span></li>
<li class="type3"><span>A long string <span>containing A much longer string </span> in its center bit.</span>
</li>
<li class="type4"><span>A much longer string of text in LI4</span></li>
</ul>
<br/>
<br/>
<br/>
<br/>
<p id="result"></p>
I've made isOverflowing recursive, and for each child node of the current node, I simply re-call it again, until there are no more child nodes. passing the return value out to the initial call allows us to see if any of the child nodes exceed the bounds of the initial node.
Hope this helps!

Drag and Drop across browser window

I'm implementing cross-window drag-and-drop for item ordering on collaborative site and I am unable to synchronize drags to the same list. I would need to detect whether I dropped an item to the same list to avoid "add", "remove" operations and use a single "move".
NOTE: this problem involves two browser windows e.g. Firefox and Chrome; unless it works in that case, it's not suitable solution. Also, I'm not looking for a server based solution.
The gist of the problem can be demonstrated by this:
function update(source, className, d){
var count = source.getElementsByClassName(className)[0];
count.innerText = parseInt(count.innerText) + d;
}
var els = document.getElementsByClassName("source");
for(var i = 0; i < els.length; i += 1){
(function(el){
el.ondragover = function(ev){
ev.dataTransfer.dropEffect = "move";
ev.preventDefault();
ev.stopPropagation();
};
el.ondrop = function(ev){
ev.preventDefault();
ev.stopPropagation();
var id = ev.dataTransfer.getData("x/source");
if(el.dataset.id == id){
update(el, "self", 1);
// how to send a value from here to dragEnd -->>
return;
}
};
})(els[i]);
}
var els = document.getElementsByClassName("element");
for(var i = 0; i < els.length; i += 1){
(function(el){
el.draggable = true;
el.ondragstart = function(ev){
grabbingFrom = el.parentNode.dataset.id;
ev.dataTransfer.effectAllowed = "all";
ev.dataTransfer.setData("x/source", el.parentNode.dataset.id);
console.log(JSON.stringify(ev.dataTransfer.getData("x/source")));
}
el.ondragend = function(ev){
var self = false; // how to get a value <--- here
if(self){
update(el, "self", 1);
}
ev.preventDefault();
}
})(els[i]);
}
.clear { clear: both; }
.source {
float: left; border: 1px solid #000; margin: 32px;
height: 100px; width: 150px;
}
.source .element { width: 32px; height: 32px; margin: 8px;
background: #AAA; margin: 8px;
line-height: 32px; text-align: center;
}
.source .element:hover {
background: hsla(0, 70%, 70%, 1);
cursor: move;
}
<div class="source" data-id="alpha" >
Alpha
<div class="self">0</div>
<div draggable="true" class="element">X</div>
</div>
<div class="source" data-id="beta" >
Beta
<div class="self">0</div>
<div draggable="true" class="element">X</div>
</div>
When you Drag from "Beta" box to a "Beta" box in cross-windows, then both "Beta" counts should be incremented. Currently only one increments. Basically how to communicate across that "Yes, I've dropped this item into a 'beta' box.". The problem boils down to sending content from ondrop event to ondragend.
I've been unable to find a suitable way to do it. Any ideas?
Or, is the answer simply that it isn't possible.

after scrolling, detect when element returns to original position

I am trying to, sort of, emulate the effect here. Essentially, during scrolling, change the css (drop shadow), and when the element comes back to original position (remove shadow).
I am able to detect scroll, but not able to figure out how to detect the return to the original un-scrolled state.
HTML
<div id="container">
<ul>
<li id="one">el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li>
</ul>
</div>
CSS
html, body {
margin: 0;
padding: 0;
height: 100%;
}
#container {
height: 100px;
width: 500px;
border: 1px solid #000000;
overflow: scroll;
}
JS (with jquery)
var p = $('#one');
var position0 = p.position().top;
$('#container').scroll(function () {
if (p.position().top != position0) {
console.log('p.position: ' + p.position().top);
$('#container').css('background-color', 'pink');
}
});
JSFIDDLE: http://jsfiddle.net/nrao89m3/
PS: From console.log it doesn't seem to return to its original value at all.
Just add an else block:
var p = $('#one');
var position0 = p.position().top;
$('#container').scroll(function () {
if (p.position().top != position0) {
console.log('p.position: ' + p.position().top);
$('#container').css('background-color', 'pink');
} else {
$('#container').css('background-color', 'white');
}
});
http://jsfiddle.net/vyjbwne2/

Categories

Resources