Prevent elements behind from the overlay being clicked - javascript

The issue
https://streamable.com/e/9z6lev (the flickering in the video is caused by the overlay being reopened every time meal plan is selected)
It "feels" like during the initial overlay open it's not the focused element and as result is's children can be clicked through :sad:
Overlay Template
The logic for the overlay is quite simple, and allow to nest any type of content inside:
<template>
<div class='swipeableWrapper'
#click.stop.prevent // not original code, just attempt to fix the issue
#touch.stop.prevent> // not original code, just attempt to fix the issue
<slot />
</div>
</template>
.swipeableWrapper {
height: 100%;
left: 0;
min-height: 100%;
position: fixed;
top: 0;
width: 100%;
z-index: 100;
}
Items List Template
<template>
<div>
...
<ListProduct v-for='(product, index) in products'
...
:showProduct='showProduct'
:key='index' />
</div>
<template>
// List Item
<template>
<div class='listProduct'
...
#click='showProduct'>
...
</div>
</template>
Intended approaches:
The following logic added to the overlay template to prevent events from bubbling:
#click.stop.prevent
#touch.stop.prevent
Global logic that will listen to opened overlay and add the following CSS class to the body element, in order to allow click on the overlay items, but still not much luck
.overlayOpened {
& * {
pointer-events: none;
touch-action: none;
}
.swipeableWrapper {
&,
& * {
pointer-events: auto;
touch-action: auto;
}
}
}
I am a bit puzzled with this dark magic behaviour and will really appreciate your opinion on the origin of the behaviour and possible solutions :bow:

Try this
#click.self.prevent="function"
Edited:
For the list item and function as prop
:showProduct="() => showProduct(item/index)"

Related

Toggle or Show/Hide

I need help toggling overlays with multiple divs. I don't want to have a separate function for each one (there's 6 with 6 different overlay popups). The onclick div will reveal the overlay popup. Help is appreciated!
I need help toggling overlays with multiple divs. I don't want to have a separate function for each one (there's 6 with 6 different overlay popups). The onclick div will reveal the overlay popup. Help is appreciated!
function on() {
document.getElementById("overlay").style.display = "block";
}
function off() {
document.getElementById("overlay").style.display = "none";
}
#overlay {
position: fixed;
display: none;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.8);
z-index: 2;
cursor: pointer;
}
#text{
position: absolute;
top: 50%;
left: 50%;
font-size: 1rem;
color: white;
transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
}
<!-- //DIV -->
<div class="row ">
<div class="col-md-6 col-lg-4 d-flex align-items-stretch" onclick="on()">
<div class="card mb-3">
<img src="img/ballet.jpg" class="embed-responsive w-100 classpic" alt="...">
<div class="card-body">
<h5 class="card-title">BALLET</h5>
</div>
</div>
</div>
<!-- //POPUP -->
<div id="overlay" onclick="off()">
<div id="text">
<h3>Ballet</h3>
<p>Ballet is an artistic dance form performed to music using precise and highly formalized set steps and gestures.
Classical ballet, which originated in Renaissance Italy and established its present form during the 19th century,
is characterized by light, graceful, fluid movements and the use of pointe shoes.
</p>
<h4>Shedule:</h4>
<p>Ages 4-8: Thursdays • 4PM<br>
Ages 9-14: Fridays • 7PM</p>
</div>
</div>
There's a problem with your approach, namely, when an element has display:none it is removed from the html tree and cannot receive a click event. Also, no two elements can share the same id attribute and so your function cannot be applied by reference to an id directly.
I've made a working snippet that achieves what I think you are after. There are undoubtedly others that would work but it's quite straight forward and works.
Firstly, arrange each of your alternative div pairs (one hidden, one visible) inside a parent div and give it a class name. This has the advantage that, if you size the container div appropriately, the content will not jump about when you swap the hidden div for visible and vice versa. Next, give classes to distinguish the (initially) hidden content from the visible div. Your markup pattern then will be repeats of:
<div class='container'>
<div class='main'>my first main content</div>
<div class='hidden'>my first hidden content </div>
</div>
In the style sheet, set the class display properties:
.hidden {
display: none;
}
.main {
display: block;
}
Then, set up a click event listener in javascript. This will take a click event from anywhere on the page.
document.addEventListener('click', event => {
})
inside the event listener, place an if block to test whether the click event was received by an element that was inside a div of .container class:
if (event.target.parentElement.className=='container') {
}
I slightly modified this, see edit note and bottom.
If the click event got that far, the click must have been recieved by the visible div inside that container (since the hidden one cannot receive click events and they are the only two elements present.
So you can go ahead and swap the classes applied to the visible div that received the click:
event.target.classList.add('hidden');
event.target.classList.remove('main');
You now have to do the opposite to the other div in the container class to make that sibling visible. The problem is, you don't know whether the hidden class was the first child, or the second child of the container div. What you do know for sure, is that the other div is a sibling of the div you just made invisible.
So we can test to see if there is a next sibling using a conditional:
if (event.target.nextElementSibling) {
event.target.nextElementSibling.classList.add('main');
event.target.nextElementSibling.classList.remove('hidden');
}
If the hidden div followed the visible one, a nextElementSibling will be found and the classes swapped. If no nextElementSibling was found, we know the other div had to come before the one we already hid.
so, an else extension of that if block can be added to switch the classes on the previousElementSibling:
...} else {
event.target.previousElementSibling.classList.add('main');
event.target.previousElementSibling.classList.remove('hidden');
} // end else;
And you're done!
I wanted to explain the logic in detail to make sure you know what's going on, but it's not that complicated.
The advantage of an approach like this is that the single event listener will cope with 1, 2, or 1,000 pairs of divs and none need any special IDs or anything other than an initial class of .main or .hidden (and that they be grouped inside a .container div.
document.addEventListener('click', event => {
if (event.target.parentElement && event.target.parentElement.className=='container') {
event.target.classList.add('hidden');
event.target.classList.remove('main');
if(event.target.nextElementSibling) {
event.target.nextElementSibling.classList.add('main');
event.target.nextElementSibling.classList.remove('hidden');
} else {
event.target.previousElementSibling.classList.add('main');
event.target.previousElementSibling.classList.remove('hidden');
} // end else;
} // end parentElement if;
}) // end click listener;
.hidden {
display: none;
border: 1px solid red;
margin: 5px;
}
.main {
display: block;
border: 1px solid black;
margin: 5px;
}
<div class='container'>
<div class='main'>my first main content</div>
<div class='hidden'>my first hidden content </div>
</div>
<div class='container'>
<div class='main'>my second main content</div>
<div class='hidden'>my second hidden content </div>
</div>
Edit the conditional to detect whether the parent element of the click event was a .container div was modified to check that the event target has a parent AND that the parent is a .container div. This prevents an error if a click is received anywhere outside of the container div.
** Displaying an Opaque Overlay in Response to Click **
Again, this solution allows the functionality to be applied to limitless div elements without the need for independent ids. Again, two classed .main and .hidden are used to decide which div has been clicked from a single event listener applied to the document rather than to multiple divs.
The basic process of displaying, and then re-hiding the (originally hidden) .overlay div is very simple:
if (element.className == 'main') {
element.parentElement.getElementsByClassName('overlay')[0].classList.remove('hidden');
}
if (element.className == 'overlay') {
element.classList.add('hidden');
}
However, a problem arises because of the use of class names, rather than ids. Namely, when the overlay is displayed, a click on it may be received by a descendent element that does not have the class name .hidden. To work properly, every descendent of the overlay div would have to be given the .hidden class and the class swapped applied for ever element inside the .hidden div. This could get very complicated if the div had many child elements (perhaps with their own descendents).
Instead, when a click is received, the target element is inspected to see if it has a relevant class (main or hidden). If it does, the script flows to the simple class switching blocks. If it has no, or a different class name however, a do-while loop examined the parent element of the click to see if it was contained in a relevant (main or hidden) class. The loop continues searching up the document tree until either a relevant element is found, or there are no more parent elements to examine.
If a parent is found to have the required class name, a reference to the element is passed onto the class switching block.
do {
if (element && (element.className == 'overlay' || element.className == 'main')) {
// foundElementClassName = element.className;
break;
} // end if;
if (element.parentElement) {
element = element.parentElement;
} else {
break;
}
} while (element.className != "overlay" || element.className != "main");
The following working snippet demonstrates the functionality. In it, three divs (coloured pink) have an associated (initially) hidden overlay div, while a fourth div has no associated overlay and should ignore clicks.
If a click is made on a pink div, it's specific overlay appears. A click anywhere on the overlay dismisses it, regardless of whether the click was received by the overlay div itself, or by a child element or deeper descendent (e.g. clicking on the text of the overlay (which is in a child h2 element still allows the correct .overlay div to have its styles switched to hide it again.
document.addEventListener('click', (event) => {
let element = event.target;
do {
if (element && (element.className == 'overlay' || element.className == 'main')) {
// foundElementClassName = element.className;
break;
} // end if;
if (element.parentElement) {
element = element.parentElement;
} else {
break;
}
} while (element.className != "overlay" || element.className != "main");
// end do-while loop;
// if a relevant element was found, the element object is stored in element variable;
if (element.className == 'main') {
element.parentElement.getElementsByClassName('overlay')[0].classList.remove('hidden');
}
if (element.className == 'overlay') {
element.classList.add('hidden');
}
}) // end click event listener;
.main {
display: block;
width: 50%;
margin: 10px;
border: 1px solid black;
background: pink;
}
.overlay {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
top: 0px;
left: 0px;
width: 100%;
min-height: 100%;
bottom: auto;
z-index: 1;
background: rgba(255,255,0,0.7);
padding: 20px;
}
.hidden {
display: none;
}
.other {
display: block;
width: 50%;
margin: 10px;
border: 1px solid black;
background: yellow;
}
<div class="container">
<div class="main">Content of div 1. Content of div 1. Content of div 1. Content of div 1. Content of div 1. Content of div 1. Content of div 1. Content of div 1 </div>
<div class="overlay hidden"><h1>overlay for first pink div</h1> </div>
</div>
<div class="other">
some other content that doesn't have an associated overlay and that should ignore clicks.
</div>
<div class="container">
<div class="main">Content of div 2. Content of div 2. Content of div 2. Content of div 2. Content of div 2. Content of div 2. Content of div 2. Content of div 2. Content of div 2. Content of div 2.</div>
<div class="overlay hidden"><h1>overlay for SECOND pink div</h1> </div>
</div>
<div class="container">
<div class="main">Content of div 3. Content of div 3. Content of div 3. Content of div 3. Content of div 3. Content of div 3. Content of div 3. Content of div 3. Content of div 3. Content of div 3. </div>
<div class="overlay hidden"><h1>overlay for Third pink div</h1> </div>
</div>

Divs Reverse scroll with translateY() in react.js

I'm trying to create with React.js a type of scroll like this one: http://spassky-fischer.fr/, where two divs are scrolling in inverse directions. They are using transform: translateY(), and I tried to implement it as well but I don't get where I'm wrong here. Here's the architecture of the projet. The current version is also here: http://noiseless-tendency.surge.sh/
App.js:
ComponentDidMount(){
window.addEventListener("scroll", this.scrollHandler);
}
...
scrollHandler = () => {
this.setState({
scrollPositionY: window.scrollY
})
}
...
render() {
return (
<div className="App">
<MainItemsContainer {...this.state} />
</div>
);
}
MainItemsContainer.js:
render() {
let style_first = {
transform: `translateY(${this.props.scrollPositionY})`,
overflow: "hidden"
}
let style_second = {
transform: `translateY(-${this.props.scrollPositionY})`,
overflow: "hidden"
}
return (
<div className="main_items_container">
<section
style={style_first}
className="main_items_container_child">
<ItemsContainer {...this.props}/>
</section>
<section
style={style_second}
className="main_items_container_child">
<ItemsContainer {...this.props}/>
</section>
</div>
);
}
App.css:
.main_items_container {
width: 100vw;
display: flex;
align-items: center;
justify-content: center;
position: fixed;
}
.main_items_container .main_items_container_child{
width: 50%;
height: 100vh;
overflow: scroll;
}
The sample site you linked actually uses the wheel event rather than the scroll event. It looks like they use this library to accomplish it: https://swiperjs.com/demos/ . My understanding is that the scroll event only fires if there's a scrollbar, which is why your event handler didn't fire.
I've created a Code Sandbox that produces the effect you want in React. It does rely on jQuery to compute the height of the whole element, and to set the initial transformation for the left half. However, those are just convenience methods, and if you don't want jQuery as a dependency, you can find workarounds for those pretty easily.
So you could use hooks and pass a custom css var through inline styling on state change to update your translateY. I have not tested but I hope you get my drift.
let elRef = useRef(null)
let[ height, setHeight] = useState()
use useLayoutEffect hook to add the event listener
useLayoutEffect (()=>{
if(!elRef) return
elRef.current.addEventListener("scroll", setHeight(elRef.current.scrollY));
return elRef.current.removeEventListener("scroll", setHeight());
}, )
and put the ref on you outermost div perhaps , so your outer div would look like
<div className='container' ref={elRef} style{{ --height: height}} >
<div className='columOne' > </div>
<div className='columTwo' > </div>
</div>
in your css (I haven't put all that is required but just showing how you custom css var is used
.container{
display: flex;
flex-direction: row
}
And within that equal sized columns
.columnOne{
transform: `translateY(calc(var(--height) * 1px)`,
overflow: "hidden"
}
.columnTwo{
transform: `translateY(-calc(var(--height) * 1px)`,
overflow: "hidden"
}
Does that help. Let me know if I could be more clear. Or use styled components and pass a prop in to achieve the same result.

Render google map with full screen on initialize

So I have been working with Google maps for quite some time now but I am stuck at a point. I have a container div in which I am rendering my map but initially its display is none. I have a floating button and I want that on its click the container's display should change to block and it should trigger the fullscreen event so the map is displayed in full screen. So far I have done this:
// html for map
<div id="searchMap" class="search-map">
<div class="embed-responsive">
<div id="searchViewMap" style="height: 540px;"></div>
<div id="restaurant-label" class="restaurant-info-container-search"></div>
</div>
</div>
initially div with id="searchMap" has display: none
// html for button to show the map
<img src="{{asset('/images/map_button.png')}}" class="content-section__map-button" onclick="showMobileMap()">
That is an image button.
// JavaScript method onclick of the button
function showMobileMap() {
$('#searchMap').css('display', 'block');
$('#searchViewMap div.gm-style button[title="Toggle fullscreen view"]').trigger('click');
}
Now the problem that I am facing is that when I click the button for the first time it displays the container with the map but it doesn't make it full screen. And when I click the button again it makes it full screen. I don't know why this is happening but I want that the map renders full screen on the first click.
try to change :
<div id="searchViewMap" style="height: 540px;"></div>
to
<div id="searchViewMap" style="width: 100%; min-height: 100vh;"></div>
or add css :
#searchMap {
height: 100%;
width: 100%;
left: 0;
position: absolute;
top: 0;
z-index: 2;
}

Click Propagation failing in Jquery

For part of the site I'm working on, I have a set of sidebars that can pull out. To have them hide when the users are done with them, I've set up a div with a click event (see below) so that whenever the user clicks somewhere outside of the sidebar, the sidebar closes. The problem that I'm running into, however, is that the click event handler is grabbing the event, running its method, and then the click event seems to stop. I've tried using return true and a few other things I've found around here and the internet, but the click event just seems to die.
$('.clickaway').click(function() {
$('body').removeClass(drawerClasses.join(' '));
return true;
});
EDIT: Here is a fiddle with an example: https://jsfiddle.net/2g7zehtn/1/
The goal is to have the drawer out and still be able to click the button to change the color of the text.
The issue is your .clickaway layer is sitting above everything that's interactive, such as your button. So clicking the button, you're actually clicking the layer.
One thing you could do is apply a higher stacking order for elements you want to interact with, above the .clickaway layer. For example, if we apply position: relative, like this:
.show-drawerHotkey .ColorButton {
position: relative;
}
The element will now be in a higher stacking order (since it comes after the clickaway, and we've applied no z-index to clickaway)
Here's a fiddle that demonstrates: https://jsfiddle.net/2g7zehtn/5/
Using this somewhat famous SO answer as a guide, you can bind to the $(document).mouseup(); event and determine whether certain "toggling" conditions apply:
[EDIT] - Example updated to illustrate clicking a link outside of the containing div.
// Resource: https://stackoverflow.com/questions/1403615/use-jquery-to-hide-a-div-when-the-user-clicks-outside-of-it
var m = $('#menu');
var c = $('#menuContainer');
var i = $('#menuIcon');
i.click(function() {
m.toggle("slow");
});
$(document).mouseup(function(e) {
console.log(e.target); // <-- see what the target is...
if (!c.is(e.target) && c.has(e.target).length === 0) {
m.hide("slow");
}
});
#menuIcon {
height: 15px;
width: 15px;
background-color: steelblue;
cursor: pointer;
}
#menuContainer {
height: 600px;
width: 250px;
}
#menu {
display: none;
height: 600px;
width: 250px;
border: dashed 2px teal;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I'm a link outside of the container
<div id="menuContainer">
<div id="menuIcon"></div>
<div id="menu"></div>
</div>

content of fixed div changes as other divs scroll past it

I'm trying to fix a div at the top of a layout that will contain a blog post's information (date posted, # of notes, permalink, etc.) and change this information as you scroll down past posts. I'm not sure if it would require any kind of javascript or just some intricate positioning using css. Here's how I would layout the posts. How can I get the specific post information from each post to change within that fixed div as the posts scroll past it?
#container {
width: 960px;
margin: 0px auto;
}
#changingpostinformation {
position: fixed;
margin: 0px auto;
}
<div id="container">
<div id="changingpostinformation">fixed post information div that's information changes as each post below reaches the top of #container</div>
<div class="post">
<h3>Post Title>
<p>This is the body of this example post.</p>
</div>
<div class="post">
<h3>Post Title>
<p>This is the body of this example post.</p>
</div>
</div>
Like #ShankarSangoli says, you should add top: 0;, and also left: 0; to #changingpostinformation (or other values to position it however you like)
You'll need some javascript to find out which post appears first on the page and show its info.
$(function() {
$(window).bind('load resize scroll', function() {
var doctop = $('body').scrollTop();
// loop over all posts, from top to bottom
$('.post').each(function() {
if ($(this).position().top > doctop) {
put_postinfo_in_fixed_div($(this));
return false; // breaks from the loop
}
});
});
});
This function runs once when page is loaded, and also when the window is resized or scrolled.
You need to implement put_postinfo_in_fixed_div() which gets an post div, and does what it says.
Use this CSS:
#changingpostinformation {
position: fixed;
top: 0px;
}

Categories

Resources