<body> only clickable where child elements fill it - javascript

Please see my JS Fiddle on both Desktop and iPhone:
https://jsfiddle.net/5jb3x5cn/4/
I am using a click eventListener on document.body:
document.body.addEventListener('click', function(){
alert(1);
})
I filled the background in blue, so that you can see the entire frame is being filled by the body and given it a height of 100vh.
I have inserted one child element for the iPhone.
On desktop, you will notice you get an alert regardless of where you click on the body. On the iPhone, the alert is only displayed when you click on the child element. I can only assume the child element 'fills' the body with some space, where you can click on - regardless of the height the body is set to.
Interestingly enough document.body.clientHeight returns a value which would suggest the body has a decent clickable size.
Now if you head over to: https://jsfiddle.net/5t8arze9/1/
You will notice that the entire body is clickable. Here I have used the touchstart eventListener.
Can anybody point me in the right direction?
Cheers,
Dan

False Code :
document.body.addEventListener('click', function(){
alert(1);
})
True Code :
document.addEventListener("click", function(){
alert(1);
});

you have to change your CSS to really fill the screen. Even the backgroud is blue, you can see that your mouse cursor is not shown correctly.
Add
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
fiddle (update): https://jsfiddle.net/5jb3x5cn/10/

In the end, a colleague this was solved as follows:
Detect iPhone via User-Agent
If iPhone, then combine touchstart, touchmove and touchend eventListeners to determine whether the user just 'clicked' or actually 'swiped'/'scrolled' the screen
If use clicked, open URL, if not - ignore
Non iPhones get the normal click listener set
Code:
if (body.addEventListener) {
if (navigator.userAgent.match(/iPad/i) != null) {
var startX = 0;
var startY = 0;
var endX = 0;
var endY = 0;
body.addEventListener('touchstart', function(event) {
startX = event.touches[0].pageX;
startY = event.touches[0].pageY;
endX = 0;
endY = 0;
}, false);
body.addEventListener('touchmove', function(event) {
endX = event.touches[0].pageX - startX;
endY = event.touches[0].pageY - startY;
}, false);
body.addEventListener('touchend', function(event) {
if (!endX && !endY) {
wallpaperClick(event);
}
startX = 0;
startY = 0;
}, false);
} else {
body.addEventListener("click", function(event) {
wallpaperClick(event);
}, !1);
}
} else {
body.attachEvent("onclick", function(event) {
wallpaperClick(event);
});
}
This however unfortunately does not quite answer why. This is somewhat vaguely explained here:
https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile

Related

ondragstart - How to remove animation?

Whenever the user starts dragging an element with draggable="true", the element has a translucent copy of the element you are dragging. Here is the example from W3Schools:
stop starting animation
Right side of the photo is important. That is the animation you get when you start dragging an element.
https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_ondrag
I have tried to use event.preventDefault(). However, the problem here is that this prevents onDrag from going at all while the element is moving and I need the data (mouse position and such) from onDrag.
It seems there are posts out there for how to stop the animation when you drop it but not start.
Just going off the example on the W3Schools site, I want the drag information without the ondragstart animation.
So if I modify the code by adding a preventDefault() on the ondragstart function:
function dragStart(event) {
console.log(event);
event.preventDefault(); //stops animation in right side of photo, but then won't let ondrag fire
event.dataTransfer.setData("Text", event.target.id);
}
function dragging(event) {
console.log(event);
document.getElementById("demo").innerHTML = "The p element is being dragged";
}
The animation you get when dragging goes away. However (as shown by my console.log) lines, the ondrag won't fire with this preventDefault on ondragstart. This is the information I need.
preventDefault inside ondrag doesn't stop that animation. Is this even possible?
SOLUTION
If anyone is wondering, I found what I needed to do in this case. You can set the image in javascript to a transparent one:
How to remove drag(Ghost) image?
so drag is still technically running but that ghostly image is gone. Wasn't thinking of right search terms.
In this case i would go for a different approach:
https://codepen.io/deibl31/pen/oNeXmPE?editors=1111
// HTML
<div id="myDiv">
</div>
// CSS
#myDiv {
position: absolute;
height: 100px;
width: 100px;
background: black;
color: white;
}
// JS
let myDiv = document.getElementById('myDiv');
let mouseDown = false;
let divPos = {x: 0, y: 0};
myDiv.addEventListener('mousedown', (event) => {
mouseDown = true;
});
window.addEventListener('mousemove', (event) => {
if (mouseDown) {
divPos.x = event.clientX;
divPos.y = event.clientY;
}
});
window.addEventListener('mouseup', (event) => {
if (mouseDown) {
myDiv.style.top = divPos.y + 'px';
myDiv.style.left = divPos.x + 'px';
}
mouseDown = false;
});
If anyone is wondering, I found what I needed to do in this case. You can set the image in JavaScript to a transparent one:
How to remove drag(Ghost) image?
so drag is still technically running but that ghostly image is gone. Wasn't thinking of right search terms.

Css :hover although element not in front

im writing a website on which pictures, ordered in a grid, are shown. I want to make it possible to drag them around with the mouse and zoom in and out with the mouse wheel. This already works so far. here is what my code looks like:
var clicked = [0,0];
var pos = [0,0]; // <-- This ist the position of the image(s)
var dragging = false
var zoom = 1;
//this function is for zooming in and out
window.onmousewheel = function(event)
{
if (event.deltaY > 0)
{
zoom *= 0.9;
}
else
{
zoom *= 1.1;
}
update(0, 0);
}
window.onmousedown = function(event)
{
clicked = [event.clientX, event.clientY];
dragging = true;
}
window.onmousemove = function(event)
{
if (dragging == false){return;}
update((event.clientX-clicked[0]),(event.clientY-clicked[1]))
}
window.onmouseup = function(event)
{
pos = [pos[0] + (event.clientX-clicked[0]), pos[1] + (event.clientY-clicked[1])];
dragging = false;
}
function update(addX, addY) //<-- this function just updades the position of the images by using the zoom and pos variable and the addX and addY parameter
All of this works very fine. But it has one Bug: When i'm start draging while the mouse is directly over one of the images, then when i release the mouse the mouseup event is not triggered an so everything is still moving until you click again with your mouse. What i also do not like is that if you are dragging while the mouse is over one of the images, it shows this standard chrome browser image moving thing.
My Idea for solving this problems was, making a div with opacity: 0; in the front over everything, which fits the whole screen. looks like this:
(css)
#controller
{
position:absolute;
top:0;
left:0;
bottom:0;
right:0;
height:100%;
width:100%;
z-index: 999;
opacity: 0;
}
(html)
<div id="controller"></div>
And now it works fine. I also can drag when i start with the mouse direct over an image. But then i realized that now i can not make any click event or an simple css :hover over one of the images anymore, obviously because the invisible div is now in the front :(
Has anyone of you an idea how two solve this problem?
Put the onmouseup inside onmousedown:
window.onmousedown = function(event)
{
clicked = [event.clientX, event.clientY];
dragging = true;
window.onmouseup = function(event)
{
pos = [pos[0] + (event.clientX-clicked[0]), pos[1] + (event.clientY-clicked[1])];
dragging = false;
}
}

Dragging elements WITHOUT jQuery

I am currently working on an online presentation software. For the sake of this question imagine it as powerpoint or keynote.
I want to be able to add elements to the slide and then drag them around (live), getting the new position, updating the database.
However I want to do this without any use of external libraries or frameworks, including jQuery.
Can anyone point me in a direction for my research? My current ideas to implement this are pretty messy. Especially the live-dragging is what's giving me headaches.
Thanks!
UPDATE!
the elements look something like this:
<div class="textelement"
data-id="528fc9026803fa9d4b03e506"
data-role="Textelement"
style=" left: 50px;
top: 50px;
z-index: 0;
width: 72px;
height: 72px;">
<div class="textnode">slide: 0 textelement: 0</div>
</div>
While HTML5 does provide native drag and drop, this isn't what you asked for. Check out this simple tutorial to accomplish dragging in vanilla JS: http://luke.breuer.com/tutorial/javascript-drag-and-drop-tutorial.aspx
There is great vanilla JS snippet available, but with one problem - when element start dragged on clickable element, it "clicks" on mouseup: see it on http://codepen.io/ekurtovic/pen/LVpvmX
<div class="draggable">
Dont click me, just drag
</div>
<script>
// external js: draggabilly.pkgd.js
var draggie = new Draggabilly('.draggable');
</script>
here is the "plugin": draggabilly
And, here is my independent solution, working by :class: of the element:
(function (document) {
// Enable ECMAScript 5 strict mode within this function:
'use strict';
// Obtain a node list of all elements that have class="draggable":
var draggable = document.getElementsByClassName('draggable'),
draggableCount = draggable.length, // cache the length
i; // iterator placeholder
// This function initializes the drag of an element where an
// event ("mousedown") has occurred:
function startDrag(evt) {
that.preventDefault();
// The element's position is based on its top left corner,
// but the mouse coordinates are inside of it, so we need
// to calculate the positioning difference:
var diffX = evt.clientX - this.offsetLeft,
diffY = evt.clientY - this.offsetTop,
that = this; // "this" refers to the current element,
// let's keep it in cache for later use.
// moveAlong places the current element (referenced by "that")
// according to the current cursor position:
function moveAlong(evt) {
evt.preventDefault();
var left = parseInt(evt.clientX - diffX);
var top = parseInt(evt.clientY - diffY);
// check for screen boundaries
if (top < 0) { top = 0; }
if (left < 0) { left = 0; }
if (top > window.innerHeight-1)
{ top = window.innerHeight-1; }
if (left > window.innerWidth-1)
{ left = window.innerWidth-1; }
// set new position
that.style.left = left + 'px';
that.style.top = top + 'px';
}
// stopDrag removes event listeners from the element,
// thus stopping the drag:
function stopDrag() {
document.removeEventListener('mousemove', moveAlong);
document.removeEventListener('mouseup', stopDrag);
}
document.addEventListener('mouseup', stopDrag);
document.addEventListener('mousemove', moveAlong);
return false;
}
// Now that all the variables and functions are created,
// we can go on and make the elements draggable by assigning
// a "startDrag" function to a "mousedown" event that occurs
// on those elements:
if (draggableCount > 0) for (i = 0; i < draggableCount; i += 1) {
draggable[i].addEventListener('mousedown', startDrag);
}
}(document));

without jquery i need to find out if the mouse is over an element, not determine when it becomes over (in case it doesn't move to trigger onmouseover)

without jquery
basically what I am looking for is the ability to see if the mouse is over a div when a countdown finishes
if the user is over the div then perform action for that div
onmouseover only triggers when the mouse crosses the threshold of the div, if the mouse hasn't moved it wouldn't trigger, so that wouldn't work
I need to determine if the mouse is currently over a div at a specific point in time, if it has moved or not from the starting point
all of my hunting has only found onmousover, and nothing to see if the mouse just happens to be there to begin with
I don't have the javascript skills to determine overall coords of div, then map mouse coords and see if it fits there... which is what I believe I need to do
After reading the second answer (the one with millions of a elements) on this SO question, I've came up with this method works without moving the mouse on page load, without involving millions of elements.
HTML
<div id=t></div>
CSS
#t {
/* for illustrative purposes */
width: 10em;
height: 5em;
background-color: #0af;
}
#t:hover {
border-top-style: hidden;
}
JavaScript
document.addEventListener('click', function () {
var c = window.getComputedStyle(document.getElementById('t')).getPropertyValue('border-top-style');
if (c === 'hidden') {
alert('Mouse in box');
} else {
alert('Mouse not in box');
}
}, false);
As stated earlier, bind to the finish event of your countdown instead of the click event on the document.
You may also use any CSS style that's changed on :hover, I chose border-top-style as it is conspicuous. If you're using a border, choose something else.
Here's a jsFiddle.
set a flag to true onmouseover and to false onmouseleave. when countdown finishes if flag is true then it is over element.
HTML
<div id="div-name">the section of the code i am working with has a countdown timer, when it reaches 0 i need to know if the mouse is over a specific box</div>
<button id="notification" onclick="javascript: letsCountIt(5);">click to start countdown</button>
JS
window.ev = false;
document.getElementById('div-name').onmouseover = function () {
window.ev = true;
console.log(window.ev);
}
document.getElementById('div-name').onmouseout = function () {
window.ev = false;
console.log(window.ev);
}
window.letsCountIt = function (cdtimer) {
cdtimer--;
document.getElementById('notification').innerHTML = cdtimer;
if (cdtimer == 0) {
if (window.ev === true) {
alert('over');
} else {
alert('not over');
}
} else {
setTimeout(function(){letsCountIt(cdtimer);}, 1000);
}
}
Look into document.elementFromPoint . When you pass an x,y to elementFromPoint, it will return whatever element (or <body>, if no other specific element) is at that point. You can easily check if this element is the element you want.
The problem then is finding out what point your mouse is at. How to get the mouse position without events (without moving the mouse)? seems to say - don't. At least use mouseMove to track the cursor. The linked question gives examples of how to do so. (Look to the lower scoring answers, as the higher ones only got points for being snarky.)
Just want to say that, I think jQuery's mouseenter and mouseleave events would make this a lot easier, but if you can't use them, maybe this will help you.
Depending on how your page is laid out, this may not be too difficult. You can get the position of your element using the following. Quoting from another answer
element.offsetLeft and element.offsetTop are the pure javascript
properties for finding an element's position with respect to its
offsetParent; being the nearest parent element with a position of
relative or absolute
So, if your element is positioned relatively to the body, so far so good (We don't need to adjust anything).
Now, if we attach an event to the document mousemove event, we can get the current coordinates of the mouse:
document.addEventListener('mousemove', function (e) {
var x = e.clientX;
var y = e.clientY;
}, false);
Now we just need to determine if the mouse falls within the element. To do that we need the height and width of the element. Quoting from another answer
You should use the .offsetWidth and .offsetHeight properties. Note
they belong to the element, not .style.
For example:
var element = document.getElementById('element');
var height = element.offsetHeight;
var width = element.offsetWidth;
Now we have all the information we need, and just need to determine if the mouse falls within the element. We might use something like this:
var onmove = function(e) {
var minX = element.offsetLeft;
var maxX = minX + element.offsetWidth;
var minY = element.offsetTop;
var maxY = minY + element.offsetHeight;
if(e.clientX >= minX && e.clientX <= maxX)
//good horizontally
if(e.clientY >= minY && e.clientY <= maxY)
//good vertically
}
This code works, but the mouse has to be moved once after page load.
var coords;
var getMouseCoordinates = function (e) {
'use strict';
return {
x: e.clientX,
y: e.clientY
};
};
document.addEventListener('mousemove', function (e) {
coords = getMouseCoordinates(e);
}, false);
document.addEventListener('click', function () {
var divCoords = document.getElementById('t').getBoundingClientRect();
if (coords.x >= divCoords.left && coords.x <= divCoords.right && coords.y >= divCoords.top && coords.y <= divCoords.bottom) {
alert('Mouse in box');
} else {
alert('Mouse not in box');
}
}, false);
You wouldn't bind to the click event of document, but rather the finish event of your countdown.
Here's an example. Try clicking in the output window.
You don't need any coordinates or mouse events, if you know a selector for that element:
if (document.querySelector('#elementSelector:hover')) {
alert('I like it when you touch me!');
}

How to propagate a click to all divs under cursor?

I have a bunch of divs positioned absolutely on top of each other. When I bind a click event to all of them, only the top div responds. How can I send the event to all divs under the cursor?
Taking FelixKling's suggestion to use document.elementFromPoint() and Amberlamps's fiddle, and employing jQuery for the DOM interactions, I ended up with the following :
$divs = $("div").on('click.passThrough', function (e, ee) {
var $el = $(this).hide();
try {
console.log($el.text());//or console.log(...) or whatever
ee = ee || {
pageX: e.pageX,
pageY: e.pageY
};
var next = document.elementFromPoint(ee.pageX, ee.pageY);
next = (next.nodeType == 3) ? next.parentNode : next //Opera
$(next).trigger('click.passThrough', ee);
} catch (err) {
console.log("click.passThrough failed: " + err.message);
} finally {
$el.show();
}
});
DEMO
try/catch/finally is used to ensure elements are shown again, even if an error occurs.
Two mechanisms allow the click event to be passed through or not :
attaching the handler to only selected elements (standard jQuery).
namespacing the click event, click.passThrough analogous to event.stopPropagation().
Separately or in combination, these mechanisms offer some flexibility in controlling the attachment and propagation of "passThrough" behaviour. For example, in the DEMO, try removing class p from the "b" element and see how the propagation behaviour has changed.
As it stands, the code needs to be edited to get different application-level behaviour. A more generalized solution would :
allow for programmatic attachment of app-specific behaviour
allow for programmatic inhibition of "passThrough" propagation, analogous to event.stopPropagation().
Both of these ambitions might be achieved by establishing a clickPassthrough event in jQuery, with underlying "passThrough" behaviour, but more work would be involved to achieve that. Maybe someone would like to have a go.
This is not as easy as you might think. This is a solution that I came up with. I only tested it in Chrome and I did not use any framework.
The following snippet is just for add a click event to every div in the document, that outputs its class name when triggered.
var divs = document.getElementsByTagName("div");
for(var i = 0; i < divs.length; i++) {
divs[i].onclick = function() {
console.log("class clicked: " + this.className);
};
}
Attaching a click event to the body element so that every single click event is noticed by our script.
if(document.addEventListener) {
document.body.addEventListener("click", countDivs);
} else if(document.attachEvent) {
document.attachEvent("onclick", countDivs);
}
Iterate through all divs that you want to check (you might want to adjust here to your preferred range of divs). Generate their computed style and check whether the mouse coordinates are within the range of the divĀ“s position plus its width and height. Do not trigger click event when the div is our source element because the click event has already been fired by then.
function countDivs(e) {
e = e || window.event;
for(var i = 0; i < divs.length; i++) {
var cStyle = window.getComputedStyle(divs[i]);
if(divs[i] !== e.target && e.pageX >= parseInt(cStyle.left) && e.pageX <= (parseInt(cStyle.left) + parseInt(cStyle.width)) && e.pageY >= parseInt(cStyle.top) && e.pageY <= (parseInt(cStyle.top) + parseInt(cStyle.height))) {
divs[i].click();
}
}
}
CSS:
.a, .b, .c {
position: absolute;
height: 100px;
width: 100px;
border: 1px #000 solid
}
.a {
top: 100px;
left: 100px;
}
.b {
top: 120px;
left: 120px;
}
.c {
top: 140px;
left: 140px;
}
HTML:
<div class="a"></div>
<div class="b"></div>
<div class="c"></div>
I also added a jsFiddle
A simple way could be to use elementFromPoint():
http://jsfiddle.net/SpUeN/1/
var clicks = 0,cursorPosition={};
$('div').click(function (e) {
if(typeof cursorPosition.X === 'undefined') {
cursorPosition.X = e.pageX;
cursorPosition.Y = e.pageY;
}
clicks++;
e.stopPropagation();
$(this).addClass('hided');
var underELEM = document.elementFromPoint(cursorPosition.X, cursorPosition.Y);
if (underELEM.nodeName.toUpperCase() === "DIV") $(underELEM).click();
else {
$('#clicks').html("Clicks: " + clicks);
$('.hided').removeClass('hided');
clicks=0;
cursorPosition = {};
}
});
If you are stacking elements absolutely it may be simpler to stack them all in a positioned container, and handle the events from this parent. You can then manipulate its children without having to measure anything.

Categories

Resources