Make parent div unclickable but children children clickable - CSS - javascript

In a scenario like this one, I want to be able to click the background div (red) through the top div, whilst still being able to click the top div's children (blue and green).
function bgclick() {
console.log('Background Is Clicked!');
}
function topclick() {
console.log('Top Is Clicked!');
}
#background {
background-color: #f33;
width: 250px;
height: 250px;
position: fixed;
top: 0;
left: 0;
z-index: -1;
}
#top {
width: 250px;
height: 250px;
position: fixed;
top: 0;
left: 0;
}
.children {
width: 50px;
height: 50px;
position: relative;
}
<div id="background" onclick="bgclick()"></div>
<div id="top">
<div class="children" onclick="topclick()" style="background-color:#3f3"></div>
<div class="children" onclick="topclick()" style="background-color:#33f"></div>
</div>
I have played around with pointer-events: none. This will only make one clickable and the other not. How can I make it so I can click the red one and get a message, along with the blue and green ones?

To make parent div unclickable but children clickable only use pointer-events.
pointer-events
The element is never the target of pointer events; however, pointer
events may target its descendant elements if those descendants have
pointer-events set to some other value. In these circumstances,
pointer events will trigger event listeners on this parent element as
appropriate on their way to/from the descendant during the event
capture/bubble phases.
More about how it works here pointer-events
.parent {
pointer-events: none;
}
.child {
pointer-events: auto;
}

You have to change the markup by nesting the #top container into the #background container.
And then simply pass the event to the topclick method and add event.stopPropagation() to it to make sure just the div on the very top gets clicked.
See the modified code:
function bgclick() {
console.log('Background Is Clicked!')
}
function topclick(event) {
event.stopPropagation();
console.log('Top Is Clicked!')
}
#background {
background-color: #f33;
width: 250px;
height: 250px;
position: fixed;
top: 0;
left: 0;
z-index: -1;
}
#top {
width: 250px;
height: 250px;
position: fixed;
top: 0;
left: 0;
}
.children {
width: 50px;
height: 50px;
position: relative;
}
<div id="background" onclick="bgclick()">
<div id="top">
<div class="children" onclick="topclick(event)" style="background-color:#3f3"> </div>
<div class="children" onclick="topclick(event)" style="background-color:#33f"> </div>
</div>
</div>

I've found a easy way to get around this problem (op).
pointer-events:none; makes it so the mouse won't interact with the element. By default, pointer-events is inherited, so just set the children as pointer-events:auto;.
function bgclick() {
console.log('Background Is Clicked!');
}
function topclick() {
console.log('Top Is Clicked!');
}
#background {
background-color: #f33;
width: 250px;
height: 250px;
position: fixed;
top: 0;
left: 0;
z-index: -1;
}
#top {
width: 250px;
height: 250px;
position: fixed;
top: 0;
left: 0;
pointer-events: none;
}
.children {
width: 50px;
height: 50px;
position: relative;
pointer-events: auto;
}
<div id="background" onclick="bgclick()"></div>
<div id="top">
<div class="children" onclick="topclick()" style="background-color:#3f3"></div>
<div class="children" onclick="topclick()" style="background-color:#33f"></div>
</div>

Related

Listening for clicks only inside a div without interfering with its children (a hitbox)?

I have the following DOM structure:
#container {
position: absolute;
width: 100px;
height: 100px;
background-color: blue;
}
#content {
position: absolute;
width: 90%;
height: 90%;
background-color: yellow;
}
#content_child {
position: absolute;
width: 30px;
height: 30px;
left: 150px;
top: 150px;
background-color: red;
}
<div id="container">
<div id="content">
<div id="content_text">Hello</div>
<div id="content_child"></div>
</div>
</div>
#content_text and #content_child would have various mouse events. I would like to detect mouse events on #container. But, if I simply attach events to #container, they will be activated by it's children, which is not desired. My requirements are the following (I'm using click events as an example):
when #container is clicked, I want its event to trigger
when a child graphically located within its div (like #content_text) is clicked, I want both the child's and #container's events to trigger
when a child graphically located outside its div (like #content_child) is clicked, I want only the child's event to trigger
I know beforehand which children will be outside and inside the div.
I can think of two approaches, both quite flawed:
Attach mouse events to #container, and for each child outside the div, add events same as those attached to #container, but make the handlers not do anything and make them non-propagating. This gets a bit unwieldy.
Add a "hit area"/"hitbox" div... somewhere. It would be a transparent div that's a child of #container (and the same size) and it would register clicks, which wouldn't propagate to the children of it's siblings, namely #content's children, because they are siblings. If I place it before #content, all of hitbox's events would be eaten by #content and they don't ever trigger. If I place it after, it's the same problem just the other way around. I can't have both listen for the same events at the same time in the same place, without passing them out to all their children.
Is there a way to achieve a hitbox sort of behavior without significant restructuring or fancy Javascript (I'm using Elm so both aren't an option)?
Attach the handler to #container. When you receive the event, if it passed through a child that is graphically outside the container, ignore it.
You can determine the path the click took to get to the container by examining target on the event object, and (if necessary) its parentNode, and so on, until you reach container.
Here's an example (in this, I hardcoded my check of whether the child is graphically inside the parent, since you said you already know that; no need to go into the work of figuring it out for a simple example):
function hook(selector, event, handler) {
var elements = document.querySelectorAll(selector);
Array.prototype.forEach.call(elements, function(el) {
el.addEventListener(event, handler, false);
});
}
// container's click handler
hook("#container", "click", function(e) {
if (passedThroughChildOutsideBox(e, this)) {
// Ignore
} else {
console.log("container click");
}
});
// child handlers
hook("#content_text, #content_child", "click", function(e) {
console.log(this.id + " click");
});
// Fake detection
function passedThroughChildOutsideBox(e, container) {
var node = e.target;
while (node && node !== container) {
if (node.id === "content_child") { // Again, just hardcoded for demo
return true;
}
node = node.parentNode;
}
return false;
}
#container {
position: absolute;
width: 100px;
height: 100px;
background-color: blue;
}
#content {
position: absolute;
width: 90%;
height: 90%;
background-color: yellow;
}
#content_child {
position: absolute;
width: 30px;
height: 30px;
left: 150px;
top: 150px;
background-color: red;
}
<div id="container">
<div id="content">
<div id="content_text">Hello</div>
<div id="content_child"></div>
</div>
</div>
Use stop propagation https://developer.mozilla.org/en/docs/Web/API/Event/stopPropagation
will prevent further propagation..
var container = document.getElementById("container");
var container_text = document.getElementById("content_text");
var container_child = document.getElementById("content_child");
container.addEventListener("click", function () {
console.log('this is the container');
});
container_text.addEventListener("click", function () {
console.log('this is the container_text');
});
container_child.addEventListener("click", function (event) {
event.stopPropagation();
console.log('this is the container_child');
});
#container {
position: absolute;
width: 100px;
height: 100px;
background-color: blue;
}
#content {
position: absolute;
width: 90%;
height: 90%;
background-color: yellow;
}
#content_child {
position: absolute;
width: 30px;
height: 30px;
left: 150px;
top: 150px;
background-color: red;
}
<div id="container">
<div id="content">
<div id="content_text">Hello</div>
<div id="content_child"></div>
</div>
</div>
Try e.stopPropagation() which will block parents click while finds child event.
$(document).ready(function() {
$("#container").click(function(e) {
alert("#container clicked");
e.stopPropagation();
});
$("#content_text").click(function(e) {
e.stopPropagation();
alert("#content_text clicked");
});
$("#content").click(function(e) {
e.stopPropagation();
alert("#content clicked");
});
});
#container {
position: absolute;
width: 100px;
height: 100px;
background-color: blue;
}
#content {
position: absolute;
width: 90%;
height: 90%;
background-color: yellow;
}
#content_text {
background: grey;
}
#content_child {
position: absolute;
width: 30px;
height: 30px;
left: 150px;
top: 150px;
background-color: red;
color: #fff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
<div id="content">
<div id="content_text">Hello</div>
<div id="content_child">hi</div>
</div>
</div>
https://codepen.io/anon/pen/zZbYvp
//codepen.io/anon/pen/zZbYvp

How to prevent get over other divs?

I have a problem...In the following example i don't want that the div who is fixed get over the div with the background red.
Here is the example:
http://jsfiddle.net/HFjU6/3645/
#fixedContainer
{
background-color:#ddd;
position: fixed;
width: 200px;
height: 100px;
left: 50%;
top: 0%;
margin-left: -100px; /*half the width*/
}
Alright, I think I get what the OP wants. He wanted a container that stays fixed on the top of the viewport, but remains confined by a parent. This behaviour is known as a conditional sticky behaviour, and is actually implemented in both Firefox (without vendor prefix) and macOS/iOS Safari (with -webkit- prefix): see position: sticky.
Therefore the easiest (but also the least cross-browser compatible) way is simply to modify your markup, such that the sticky element stays within a parent, and you declare position: sticky on it:
* {
margin: 0;
padding: 0;
}
#fixedContainer {
background-color: #ddd;
position: -webkit-sticky;
position: sticky;
width: 200px;
height: 100px;
left: 50%;
top: 0%;
transform: translate(-50%, 0); /* Negative left margins do not work with sticky */
}
#div1 {
height: 200px;
background-color: #bbb;
}
#div1 .content {
position: relative;
top: -100px; /* Top offset must be manually calculated */
}
#div2 {
height: 500px;
background-color: red;
}
<div id="div1">
<div id="fixedContainer">I am a sticky container that stays within the sticky parent</div>
<div class="content">Sticky parent</div></div>
<div id="div2">Just another element</div>
An alternative would be to use a JS-based solution. In this case, you do not actually have to modify your markup. I have changed the IDs for easier identification of the elements, however.
The gist of the logic is this:
When the scroll position does not exceed the bottom of the parent minus the outer height of the sticky content, then we do not do anything.
When the scroll position exceeds the bottom of the parent minus the outer height of the sticky content, we dynamically calculate the top position of the sticky content so that it remains visually in the parent.
$(function() {
$(window).scroll(function() {
var $c = $('#sticky-container'),
$s = $('#sticky-content'),
$t = $(this); // Short reference to window object
if ($t.scrollTop() > $c.outerHeight() - $s.outerHeight()) {
$s.css('top', $c.offset().top + $c.outerHeight() - $t.scrollTop() - $s.outerHeight());
} else {
$s.css('top', 0);
}
});
});
* {
margin: 0;
padding: 0;
}
div {
height: 500px;
background-color: red;
}
#sticky-container {
background-color: #bbb;
height: 200px;
}
#sticky-content {
background-color: #ddd;
position: fixed;
width: 200px;
height: 100px;
margin-left: -100px;
left: 50%;
top: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="sticky-content">Sticky content that stays within the bounds of #div1</div>
<div id="sticky-container">Sticky confinement area</div>
<div>Other content</div>
Old answer before OP clarified the question appropriately:
Just give them the appropriate z-index values. In this case, you want to:
Do not use static positioning. This can be done by using position: relative for the large elements, in conjunction with the originally position: fixed element.
Assign the appropriate stacking order. The grey <div> element to have the lowest z-index, followed by the position fixed element, and then by the red element.
There are some catchalls to stacking though: the stacking context is reset when you traverse up or down the node tree. For example, the example will not work if the elements are not siblings.
Here is a proof-of-concept example, modified from your fiddle so that inline CSS is removed.
* {
margin: 0;
padding: 0;
}
#fixedContainer {
background-color: #ddd;
position: fixed;
width: 200px;
height: 100px;
left: 50%;
top: 0%;
margin-left: -100px;
z-index: 2;
}
#div1 {
height: 200px;
background-color: #bbb;
position: relative;
z-index: 1;
}
#div2 {
height: 500px;
background-color: red;
position: relative;
z-index: 3;
}
<div id="fixedContainer">z-index: 2</div>
<div id="div1">z-index: 1</div>
<div id="div2">z-index: 3</div>
Just give the z-index.
Hope it helps...
http://jsfiddle.net/HFjU6/1/#run
#fixedContainer {
background-color:#ddd;
position: fixed;
width: 200px;
height: 100px;
left: 50%;
top: 0%;
margin-left: -100px; /*half the width*/
z-index: 2;
}
.div-red {
position: relative;
z-index: 5;
}
<div id="fixedContainer"></div>
<div style="height:200px;background-color:#bbb;"></div>
<div style="height:500px;background-color:red;" class="div-red"></div>

jQuery mouse enter/leave not correctly detecting hovering area

I looked around what I would like to achieve, but I wasn't able to find any suitable answer.
Basically I can't make the code to correctly detect mouse entering and leaving a div that is overlapping another div.
This is my current situation:
Working demo: http://jsfiddle.net/2f5xx73y/
HTML:
<div style='height: 100%; width: 100%;padding: 30%;'>
<div class='box'>
<div class='inner-box'>Merry xmas!</div>
</div>
<div class='box'>
<div class='inner-box'>Happy new year!</div>
</div>
</div>
CSS:
.box {
height: 100px;
width: 100px;
display: inline-block;
position: relative;
background-color: green;
}
.inner-box {
height: 100%;
width: 100%;
position: absolute;
opacity: 0;
z-index: 1;
}
.zoomed-inner-box {
height: 160%;
width: 160%;
top: -30%;
left: -30%;
position: absolute;
background-color: red;
z-index: 1;
}
JS:
$(".inner-box").mouseenter(function () {
$(this).attr("class", "zoomed-inner-box");
});
$(".inner-box").mouseleave(function () {
$(this).attr("class", "inner-box");
});
As you can see there are two boxes which become bigger when hovered overlapping the other box.
Going right to left everything works fine, in fact the red div goes away as soon as the mouse leave it. This doesn't happen in the opposite direction, where a mouseleave event it's fired as soon as the cursor enters the green div behind the red one, while I want the red div to go away when the mouse completely leave it.
I also tried using the :hover selector for the inner-box class but it has the exact same behaviour. Do you know a nice solution to this problem?
Just change the z-index on .zommed-inner-box to overwrite the .inner-box's z-index. That way the currently hovered box has a higher z-index than .inner-box :
.inner-box {
height: 100%;
width: 100%;
position: absolute;
opacity: 0;
z-index: 1; <---- original z-index
}
.zoomed-inner-box {
height: 160%;
width: 160%;
top: -30%;
left: -30%;
position: absolute;
background-color: red;
z-index: 2; <---- higher z-index
}
FIDDLE

Creating hover-visible buttons

I am trying to create some options that are hidden unless the user goes with the mouse in a specific area. Let's take an example: Google+ profile page:
When you go with the mouse cursor on the picture, the button appears.
Here is what I tried:
var $button = $("#button");
$("#profile-picture").on("mouseover", function() {
$button.show();
}).on("mouseout", function() {
$button.hide();
});
#profile-picture {
width: 150px;
height: 100px;
}
#button {
position: absolute;
display: none;
width: 30px;
height: 30px;
top: 45px;
left: 70px;
opacity: 0.75;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<img src="http://datastore01.rediff.com/h450-w670/thumb/69586A645B6D2A2E3131/ckez1n08svw8f3en.D.0.Sidharth-Malhotra-Student-of-the-Year-Photo.jpg" id="profile-picture">
<img src="http://img3.wikia.nocookie.net/__cb20090227194712/java/images/0/0e/Camera_icon.gif" id="button" />
The problem is that when I go with the cursor over the #button, it flickers. What can I do?
The easiest method is placing them both in the same div, and then using mouseover/out for that div. Example: http://jsfiddle.net/1g24mhhz/
HTML:
<div id="profile-picture">
<img src="http://datastore01.rediff.com/h450-w670/thumb/69586A645B6D2A2E3131/ckez1n08svw8f3en.D.0.Sidharth-Malhotra-Student-of-the-Year-Photo.jpg" class="profile">
<img src="http://img3.wikia.nocookie.net/__cb20090227194712/java/images/0/0e/Camera_icon.gif" id="button" />
</div>
CSS edits:
#profile-picture .profile {
width: 150px;
height: 100px;
}
EDIT: You should probably not use an ID for the div, since you probably have multiple profiles on a page. This was just to show it with the code you had already used.
A simple css approach. You can have a click event on the button :)
$('#button').on('click', function() {
alert('I am clickable');
});
#profile-picture,
.hover-wrap {
width: 150px;
height: 100px;
}
#button {
position: absolute;
display: none;
width: 30px;
height: 30px;
top: 0px;
left: 0px;
bottom: 0;
right: 0;
margin: auto;
opacity: 0.75;
cursor: pointer;
}
.hover-wrap {
position: relative;
}
.hover-wrap:hover #button {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div class="hover-wrap">
<img src="http://datastore01.rediff.com/h450-w670/thumb/69586A645B6D2A2E3131/ckez1n08svw8f3en.D.0.Sidharth-Malhotra-Student-of-the-Year-Photo.jpg" id="profile-picture">
<img src="http://img3.wikia.nocookie.net/__cb20090227194712/java/images/0/0e/Camera_icon.gif" id="button" />
</div>
You can use CSS:hover properties to show/hide the button, no Javascript needed.
The trick is a sibling selector:
#profile-picture:hover + #button, #button:hover{
display:block;
}
Try this:
http://jsfiddle.net/6s9200ab/

.toggleClass not toggling a certain class, but is working on all others

So I have a page on my website that has some navigation elements that stick on the page when the user scrolls past a certain point. There are three of them, one on the top, one on the left, and one on the right. HTML and CSS is as follows:
<div id="nav" class="nav">
<!--STUFF CONTAINED IN TOP NAV BAR-->
</div>
<div class="right" id="right">
<!--STUFF CONTAINED IN RIGHT NAV-->
</div>
<div class="left" id="left">
<!--STUFF CONTAINED IN LEFT NAV BAR-->
</div>
.nav {
position: absolute;
top: 108px;
height: 45px;
width: 100%;
left: 0;
right: 0;
}
.nav_sticky {
position: fixed;
top: 0;
height: 45px;
left: 0;
right: 0;
background-image: url(images/backgrounds/stardust_#2X.png);
z-index: 10;
}
.right {
width: 200px;
height: 900px;
position: absolute;
right: 50%;
margin-right: -538px;
top: 153px;
}
.right_sticky {
width: 200px;
height: 900px;
position: fixed;
right: 50%;
margin-right: -538px;
top: 45px;
}
.left {
width: 200px;
height: 900px;
position: absolute;
left: 50%;
margin-left: -538px;
top: 153px;
}
.left_stick {
width: 200px;
height: 900px;
position: fixed;
left: 50%;
margin-left: -538px;
top: 45px;
}
I then use the follow JQuery to cause these elements to stick.
<script>
$(document).ready(function(){
var navPos = $('#nav').offset().top;
$(window).scroll(function () {
var scrollTop = $(this).scrollTop();
if (scrollTop >= navPos) {
var classNamee = $('#nav').attr('class');
console.log(classNamee);
if (classNamee === "nav") {
$("#nav").toggleClass('nav nav_sticky');
$("#right").toggleClass('right right_sticky');
$("#left").toggleClass('left left_stick');
}
}
if (scrollTop <= navPos) {
var className = $('#nav').attr('class');
console.log(className);
if (className === "nav_sticky") {
$("#nav").toggleClass('nav_sticky nav');
$("#right").toggleClass('right_sticky right');
$("#left").toggleClass('left left_stick');
}
}
});
});
</script>
Here's my problem. This works perfectly for the top and right navs, however no matter what I try, the left nav continues to scroll when the others have stopped. I thought it may have been a typo in the css class, but when I looked in the inspector, the .toggleClass function doesn't even change the class on the #left element when it does on the other two. Any ideas as to what could be causing this?
If I copy/paste your sample code as-is to jsFiddle and run it, when you scroll down far enough, it does correctly toggle everything to *_sticky classes, but something about the negative margin-right on the right class element seems to reset the scroll to the top (at least in Chrome) when it flips between .right and .right-stick. When the scroll gets reset, it also reruns your event handeler and changes all the classes back.
Try removing these lines from your CSS and see if the behavior works right (it does in Chrome in a jsFiddle)
.right {
...
/*margin-right: -538px;*/
.right-stick {
...
/*margin-right: -538px;*/

Categories

Resources