I'd like to detect a click inside or outside a div area. The tricky part is that the div will contain other elements and if one of the elements inside the div is clicked, it should be considered a click inside, the same way if an element from outside the div is clicked, it should be considered an outside click.
I've been researching a lot but all I could find were examples in jquery and I need pure javascript.
Any suggestion will be appreciated.
It depends on the individual use case but it sounds like in this example there are likely to be other nested elements inside the main div e.g. more divs, lists etc. Using Node.contains would be a useful way to check whether the target element is within the div that is being checked.
window.addEventListener('click', function(e){
if (document.getElementById('clickbox').contains(e.target)){
// Clicked in box
} else{
// Clicked outside the box
}
});
An example that has a nested list inside is here.
You can check if the clicked Element is the div you want to check or not:
document.getElementById('outer-container').onclick = function(e) {
if(e.target != document.getElementById('content-area')) {
console.log('You clicked outside');
} else {
console.log('You clicked inside');
}
}
Referring to Here.
you can apply if check for that inside your click event
if(event.target.parentElement.id == 'yourID')
In Angular 6 and IONIC 3, I do same as here:
import {Component} from 'angular/core';
#Component({
selector: 'my-app',
template: `
<ion-content padding (click)="onClick($event)">
<div id="warning-container">
</div>
</ion-content>
`
})
export class AppComponent {
onClick(event) {
var target = event.target || event.srcElement || event.currentTarget;
if (document.getElementById('warning-container').contains(target)){
// Clicked in box
} else{
// Clicked outside the box
}
}
}
This working fine on web/android/ios.
It might be helpful for someone, Thanks.
Try this solution it uses pure javascript and it solves your problem. I added css just for better overview... but it is not needed.
document.getElementById('outer-div').addEventListener('click', function(){
alert('clicked outer div...');
});
document.getElementById('inner-div').addEventListener('click', function(e){
e.stopPropagation()
alert('clicked inner div...');
});
#outer-div{
width: 200px;
height: 200px;
position: relative;
background: black;
}
#inner-div{
top: 50px;
left: 50px;
width: 100px;
height: 100px;
position: absolute;
background: red;
}
<div id="outer-div">
<div id="inner-div">
</div>
</div>
I came up with a hack for this that's working well for me and that might help others.
When I pop up my dialog DIV, I simultaneously display another transparent DIV just behind it, covering the whole screen.
This invisible background DIV closes the dialog DIV onClick.
This is pretty straightforward, so I'm not going to bother with the code here. LMK in the comments if you want to see it and I'll add it in.
HTH!
closePopover () {
var windowBody = window
var popover = document.getElementById('popover-wrapper') as HTMLDivElement;
windowBody?.addEventListener('click', function(event){
if(popover === event.target) {
console.log("clicked on the div")
}
if(popover !== event.target) {
console.log("clicked outside the div")
}
})
}
}
I recently needed a simple vanilla JS solution which solves for:
Ignoring specific selectors including whether a parent contains one of these selectors
Ignoring specific DOM nodes
This solution has worked quite well in my app.
const isClickedOutsideElement = ({ clickEvent, elToCheckOutside, ignoreElems = [], ignoreSelectors = [] }) => {
const clickedEl = clickEvent.srcElement;
const didClickOnIgnoredEl = ignoreElems.filter(el => el).some(element => element.contains(clickedEl) || element.isEqualNode(clickedEl));
const didClickOnIgnoredSelector = ignoreSelectors.length ? ignoreSelectors.map(selector => clickedEl.closest(selector)).reduce((curr, accumulator) => curr && accumulator, true) : false;
if (
isDOMElement(elToCheckOutside) &&
!elToCheckOutside.contains(clickedEl) &&
!didClickOnIgnoredEl &&
!didClickOnIgnoredSelector
){
return true;
}
return false;
}
const isDOMElement = (element) => {
return element instanceof Element || element instanceof HTMLDocument;
}
In React you can use useClickOutside hook from react-cool-onclickoutside.
Demo from Github:
import { useClickOutside } from 'use-events';
const Example = () => {
const ref1 = React.useRef(null);
const ref2 = React.useRef(null);
const [isActive] = useClickOutside([ref1, ref2], event => console.log(event));
return (
<div>
<div ref={ref1} style={{ border: '1px dotted black' }}>
You are {isActive ? 'clicking' : 'not clicking'} outside of this div
</div>
<br />
<div ref={ref2} style={{ border: '1px dotted black' }}>
You are {isActive ? 'clicking' : 'not clicking'} outside of this div
</div>
</div>
);
};
Live demo
Related
I am using ReactJS on an App and currently need to be able to print some elements from the page on user's request (click on a button).
I chose to use the CSS media-query type print (#media print) to be able to check if an element should be printed, based on a selector that could be from a class or attribute on an Element. The strategy would be to hide everything but those "printable" elements with a stylesheet looking like:
#media print {
*:not([data-print]) {
display: none;
}
}
However, for this to work I need to also add the chosen print selector (here the attribute data-print) on every parent element each printable element has.
To do that here's what I've tried so far:
export default function PrintButton() {
useEffect(() => {
const handleBeforePrint = () => {
printNodeSelectors.forEach((selector) => {
const printableElement = document.querySelector(selector);
if (printableElement != null) {
let element = printableElement;
while (element.parentElement) {
element.setAttribute("data-print", "");
element = element.parentElement;
}
element.setAttribute("data-print", "");
}
});
};
const handleAfterPrint = () => {
printNodeSelectors.forEach((selector) => {
const printableElement = document.querySelector(selector);
if (printableElement != null) {
let element = printableElement;
while (element.parentElement) {
element.removeAttribute("data-print");
element = element.parentElement;
}
element.removeAttribute("data-print");
}
});
};
window.addEventListener("beforeprint", handleBeforePrint);
window.addEventListener("afterprint", handleAfterPrint);
return () => {
window.removeEventListener("beforeprint", handleBeforePrint);
window.removeEventListener("afterprint", handleAfterPrint);
};
}, []);
return <button onClick={() => window.print()}>Print</button>;
}
With printNodeSelectors being a const Array of string selectors.
Unfortunately it seems that React ditch out all my dirty DOM modification right after I do them 😭
I'd like to find a way to achieve this without having to manually put everywhere in the app who should be printable, while working on a React App, would someone knows how to do that? 🙏🏼
Just CSS should be enough to hide all Elements which do not have the data-print attribute AND which do not have such Element in their descendants.
Use the :has CSS pseudo-class (in combination with :not one) to express that 2nd condition (selector on descendants):
#media print {
*:not([data-print]):not(:has([data-print])) {
display: none;
}
}
Caution: ancestors of Elements with data-print attribute would not match, hence their text nodes (not wrapped by a tag) would not be hidden when printing:
<div>
<span>should not print</span>
<span data-print>but this should</span>
Caution: text node without tag may be printed...
</div>
Demo: https://jsfiddle.net/6x34ad50/1/ (you can launch the print preview browser feature to see the effect, or rely on the coloring)
Similar but just coloring to directly see the effect:
*:not([data-print]):not(:has([data-print])) {
color: red;
}
<div>
<span>should not print (be colored in red)</span>
<span data-print>but this should</span>
Caution: text node without tag may be printed...
</div>
After some thoughts, tries and errors it appears that even though I managed to put the attribute selector on the parents I completely missed the children of the elements I wanted to print! (React wasn't at all removing the attributes from a mysterious render cycle in the end)
Here's a now functioning Component:
export default function PrintButton() {
useEffect(() => {
const handleBeforePrint = () => {
printNodeSelectors.forEach((selector) => {
const printableElement = document.querySelector(selector);
if (printableElement != null) {
const elements: Element[] = [];
// we need to give all parents and children a data-print attribute for them to be displayed on print
const addParents = (element: Element) => {
if (element.parentElement) {
elements.push(element.parentElement);
addParents(element.parentElement);
}
};
addParents(printableElement);
const addChildrens = (element: Element) => {
elements.push(element);
Array.from(element.children).forEach(addChildrens);
};
addChildrens(printableElement);
elements.forEach((element) => element.setAttribute("data-print", ""));
}
});
};
const handleAfterPrint = () => {
document.querySelectorAll("[data-print]").forEach((element) => element.removeAttribute("data-print"));
};
window.addEventListener("beforeprint", handleBeforePrint);
window.addEventListener("afterprint", handleAfterPrint);
return () => {
window.removeEventListener("beforeprint", handleBeforePrint);
window.removeEventListener("afterprint", handleAfterPrint);
};
}, []);
return <button onClick={() => window.print()}>Print</button>;
}
I usually don't like messing with the DOM while using React but here it allows me to keep everything in the component without having to modify anything else around (though I'd agree that those printNodeSelectors need to be chosen from outside and aren't dynamic at the moment)
I am creating a list of divs, which was created with map.
function renderButtons(){
const options = [...Array(10).keys()] // returns [0,1,2...9]
return _.map(options, (option)=> renderOption(option))
}
function renderOption(option:number){
return (
<div className="option-container" onClick={() => setLowerContainerVisible(true)}>
<img alt="" src={"./images/feedback-icons/icon-"+option.toString()+".svg"}/>
{option+1}
</div>
)
}
this renders a list of divs, and I was able to change each div background, when hover, like this:
.option-container{
width: 76px;
height: 100px;
background-color: #7777ff;
display: flex;
}
.option-container:hover{
background-color: #adadf3;
}
I wish to be able to click on a div, and change its background color to white. everything I try will change the background of all the 10 divs to white. How can I make it so only the clicked one is changed?
I suggest that you use renderOption and renderButtons as two components rather than plain functions. In the RenderButtons component, you can use some state to maintain which item is clicked, and within RenderOption you can control whether the background color is white or not based on wehther or not the current rendered button is the clicked option. In your .map() method, you can use component rather than a function call <RenderOption option={option} ... />.
See example below:
const {useState} = React;
function RenderButtons() {
const [clickedItem, setClickedItem] = useState(-1);
return Array.from(
Array(10).keys(),
option => <RenderOption isClicked={clickedItem === option} option={option} setClicked={setClickedItem}/>
);
}
function RenderOption({isClicked, option, setClicked}) {
const handleClick = () => {
// setLowerContainerVisible(true) / other code to run when you click
setClicked(option); // set to current option
}
return (
<div className={"option-container " + (isClicked ? "clicked" : "")} onClick={handleClick}>
{option+1}
</div>
)
}
ReactDOM.render(<RenderButtons />, document.body);
.option-container {
width: 76px;
height: 100px;
background-color: #7777ff;
display: flex;
}
.option-container.clicked, .option-container:hover {
background-color: #adadf3;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
The className is a little messy as it involves a ternary, to clean this up it might be worth looking into using a node package such as classnames which allows you to easily build a list of classes based on conditions.
Do it in the event listener function:
<div className="option-container" onClick={highlightAndsetLowerContainerVisible}>
function highlightAndsetLowerContainerVisible(event){
event.preventDefault();
setLowerContainerVisible(true)
event.currentTarget.style.backgroundColor = "red";
}
You then might also want to reset the background color of the other divs
i am trying to create a popupdialog to be shown when user clicks on a button. for that i am using portal.
i want it to look like in the picture below,
So basically, when user clicks on the add button i want the popup dialog to display like in the picture above.
in the popup component i want to render overlay with children. and when user clicks on overlay div the popup should close.
I have something that kind of works without using Portal and is like below,
below is my code that is without using Portal,
function Parent({isDialogOpen, setDialogOpen, setSomething}: Props) {
const [isClicked, setIsClicked] = React.useState(false);
const handleButtonClick = () => {
if (setIsDialogOpen) setIsDialogOpen(!isDialogOpen);
if (setSomething) setSomething(isDialogOpen);
setIsClicked(!isClicked);
};
return (
<button onClick={handleButtonClick}>click</button>
{isDialogOpen && isClicked &&
<Overlay>
<Dialog>
//some divs
</Dialog>
</Overlay>
}
);
}
const Overlay = styled.div`
position: fixed;
padding-top:60px;
bottom: 40px;
top: 0;
left: 0;
right: 0;
backdrop-filter: blur(8px);
z-index: 100;
display: flex;
justify-content: center;
align-items: center;
`;
const Dialog = styled.div`
padding: 16px;
width: 384px;
max-height: calc(100% - 200px);
display: flex;
flex-direction: column;
`;
Now i am rewriting above using portal like below,
function Parent({isDialogOpen, setDialogOpen, setSomething}: Props) {
const [isClicked, setIsClicked] = React.useState(false);
const handleButtonClick = () => {
if (setIsDialogOpen) setIsDialogOpen(!isDialogOpen);
if (setSomething) setSomething(isDialogOpen);
setIsClicked(!isClicked);
};
return (
<button onClick={handleButtonClick}>click</button>
{isDialogOpen && isClicked &&
<Popup setSomething={setSomething} setIsDialogOpen={setIsDialogOpen} setIsClicked=
{setIsClicked}>
<Dialog>
//some divs
</Dialog>
</Overlay>
}
);
}
function Popup({setIsClicked, setSomething, setIsDialogOpen, children}: Props) {
return ReactDom.createPortal(
<>
<Overlay
onClick={() => {
if (setIsDialogOpen) setIsDialogOpen(false);
if (setSomething) setSomething(true);
setIsClicked(false);
}}
>
{children}
</Overlay>
</>,
//dont know what to pass here
);
}
Basically as seen in picture above, i want to render the overlay with dialog.
now in popup component i want to create div with classname 'popup' and find the div element with class navbar and attach this div popup to the navbar div
and pass this div element with class popup in the reactDOM.createPortal.
i am new to react and not sure how to do this. could someone help me with this.
thanks.
As I mentioned to you in the comment, react doesn't create the dom node for you. You must do it yourself. How you do it depends on your needs. the most basic example i can think of is below:
When component mounts first time, we need to create the portal and insert it into document.body
once we are sure portal exists, we can render into the portal using our dom ref.
function Popup({setIsClicked, setSomething, setIsDialogOpen, children}: Props) {
const [portal,setPortal] = React.useState<HTMLDivElement|null>( (document.getElementById('my-portal') as HTMLDivElement)||null);
const createPortalIfNotExists = React.useCallback(()=>{
if(portal===null){
const el = document.createElement('div');
el.id='my-portal';
document.body.appendChild(el);
// switch this line for the one above if you want it to be first in tree
// document.body.insertBefore(el, document.body.firstChild);
setPortal(document.getElementById('my-portal') as HTMLDivElement);
}
},[portal]);
createPortalIfNotExists();
if(portal===null){
return null;
}
return ReactDom.createPortal(
<>
<Overlay
onClick={() => {
if (setIsDialogOpen) setIsDialogOpen(false);
if (setSomething) setSomething(true);
setIsClicked(false);
}}
>
{children}
</Overlay>
</>,
portal
);
}
This is just one possible way of doing it. There are other more advanced use cases where you would have the portal be rendered by some other element in your component tree. But this should be enough to get you started. Also, i haven't tested this as i don't have tsc/tslint on this machine so YMMV
I'm just in the process of learning how JavaScript classes work and I'm just looking for some advice on how to achieve something quite simple I hope regarding animating some elements.
I have created a class named myAnimation, the constructor takes in 1 argument which is an element. All its doing is fading a heading out and in, all very simple. It works fine when there is just one heading element on the page, I'm just not to sure how I go about getting it to work with more than one heading.
Please excuse my naivety with this; it's all very new to me, this is just a basic example I have managed to make myself to try and help myself understand how it works.
class myAnimation {
constructor(element) {
this.element = document.querySelector(element);
}
fadeOut(time) {
if (this.element.classList.contains('fadeout-active')) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
button.textContent = 'Hide Heading';
} else {
this.element.style.opacity = 0;
this.element.style.transition = `all ${time}s ease`;
this.element.classList.add('fadeout-active');
button.textContent = 'Show Heading';
}
}
}
const heading = new myAnimation('.heading');
const button = document.querySelector('.button');
button.addEventListener('click', () => {
heading.fadeOut(1);
});
<div class="intro">
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main">
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
After my comment I wanted to make the script run in a way I thought it might have been intended by the OP.
Even though it demonstrates what needs to be done in order to run properly, the entire base design proofs to be not fitting to what the OP really might need to achieve.
The class is called Animation but from the beginning it was intermingling element-animation and changing state of a single somehow globally scoped button.
Even though running now, the design does not proof to be a real fit because one now passes the element that is going to be animated and the button it shall interact with altogether into the constructor.
The functionality is grouped correctly, just the place and the naming doesn't really fit.
The OP might think about a next iteration step of the provided code ...
class Animation {
constructor(elementNode, buttonNode) {
this.element = elementNode;
this.button = buttonNode;
// only in case both elements were passed ...
if (elementNode && buttonNode) {
// couple them by event listening/handling.
buttonNode.addEventListener('click', () => {
// - accessing the `Animation` instance's `this` context
// gets assured by making use of an arrow function.
this.fadeOut(1);
});
}
}
fadeOut(time) {
if (this.element.classList.contains('fadeout-active')) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
this.button.textContent = 'Hide Heading';
} else {
this.element.style.opacity = 0;
this.element.style.transition = `all ${time}s ease`;
this.element.classList.add('fadeout-active');
this.button.textContent = 'Show Heading';
}
}
}
function initializeAnimations() {
// get list of all elements that have a `heading` class name.
const headingList = document.querySelectorAll('.heading');
// for each heading element do ...
headingList.forEach(function (headingNode) {
// ... access its parent element and query again for a single button.
const buttonNode = headingNode.parentElement.querySelector('.button');
// if the related button element exists ...
if (buttonNode) {
// ... create a new `Animation` instance.
new Animation(headingNode, buttonNode);
}
});
}
initializeAnimations();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro">
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main">
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
... new day, next possible iteration step ...
The 2nd iteration separates concerns.
It does so by renaming the class and implementing only class specific behavior. Thus a FadeToggle class provides just toggle specific functionality.
The code then gets split into two functions that handle initialization. For better reuse the initializing code and the html structure need to be refactored into something more generic. The data attribute of each container that features a trigger-element for fading a target element will be used as a configuration storage that provides all necessary information for the initializing process. (One even can provide individual transition duration values.)
Last there is a handler function that is implemented in a way that it can be reused by bind in order to generate a closure which provides all the necessary data for each trigger-target couple.
class FadeToggle {
// a clean fade-toggle implementation.
constructor(elementNode, duration) {
duration = parseFloat(duration, 10);
duration = Number.isFinite(duration) ? duration : 1;
elementNode.style.opacity = 1;
elementNode.style.transition = `all ${ duration }s ease`;
this.element = elementNode;
}
isFadeoutActive() {
return this.element.classList.contains('fadeout-active');
}
toggleFade(duration) {
duration = parseFloat(duration, 10);
if (Number.isFinite(duration)) {
this.element.style.transitionDuration = `${ duration }s`;
}
if (this.isFadeoutActive()) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
} else {
this.element.style.opacity = 0;
this.element.classList.add('fadeout-active');
}
}
}
function handleFadeToggleWithBoundContext(/* evt */) {
const { trigger, target } = this;
if (target.isFadeoutActive()) {
trigger.textContent = 'Hide Heading';
} else {
trigger.textContent = 'Show Heading';
}
target.toggleFade();
}
function initializeFadeToggle(elmNode) {
// parse an element node's fade-toggle configuration.
const config = JSON.parse(elmNode.dataset.fadeToggleConfig || null);
const selectors = (config && config.selectors);
if (selectors) {
try {
// query both the triggering and the target element
const trigger = elmNode.querySelector(selectors.trigger || null);
let target = elmNode.querySelector(selectors.target || null);
if (trigger && target) {
// create a `FadeToggle` target type.
target = new FadeToggle(target, config.duration);
// couple trigger and target by event listening/handling ...
trigger.addEventListener(
'click',
handleFadeToggleWithBoundContext.bind({
// ... and binding both as context properties to the handler.
trigger,
target
})
);
}
} catch (exception) {
console.warn(exception.message, exception);
}
}
}
function initializeEveryFadeToggle() {
// get list of all elements that contain a fade-toggle configuration
const configContainerList = document.querySelectorAll('[data-fade-toggle-config]');
// do initialization for each container separately.
configContainerList.forEach(initializeFadeToggle);
}
initializeEveryFadeToggle();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"},"duration":3}'>
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"}}'>
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
... afternoon, improve the handling of state changes ...
There is still hard wired data, written directly into the code. In order to get rid of string-values that will be (re)rendered every time a toggle-change takes place one might give the data-based configuration-approach another chance.
This time each triggering element might feature a configuration that provides state depended values. Thus the initialization process needs to take care of retrieving this data and also of rendering it according to the initial state of a fade-toggle target.
This goal directly brings up the necessity of a render function for a trigger element because one needs to change a trigger's state not only initially but also with every fade-toggle.
And this again will change the handler function in a way that in addition it features bound state values too in order to delegate such data to the render process ...
class FadeToggle {
// a clean fade-toggle implementation.
constructor(elementNode, duration) {
duration = parseFloat(duration, 10);
duration = Number.isFinite(duration) ? duration : 1;
elementNode.style.opacity = 1;
elementNode.style.transition = `all ${ duration }s ease`;
this.element = elementNode;
}
isFadeoutActive() {
return this.element.classList.contains('fadeout-active');
}
toggleFade(duration) {
duration = parseFloat(duration, 10);
if (Number.isFinite(duration)) {
this.element.style.transitionDuration = `${ duration }s`;
}
if (this.isFadeoutActive()) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
} else {
this.element.style.opacity = 0;
this.element.classList.add('fadeout-active');
}
}
}
function renderTargetStateDependedTriggerText(target, trigger, fadeinText, fadeoutText) {
if ((fadeinText !== null) && (fadeoutText !== null)) {
if (target.isFadeoutActive()) {
trigger.textContent = fadeinText;
} else {
trigger.textContent = fadeoutText;
}
}
}
function handleFadeToggleWithBoundContext(/* evt */) {
// retrieve context data.
const { target, trigger, fadeinText, fadeoutText } = this;
target.toggleFade();
renderTargetStateDependedTriggerText(
target,
trigger,
fadeinText,
fadeoutText
);
}
function initializeFadeToggle(elmNode) {
// parse an element node's fade-toggle configuration.
let config = JSON.parse(elmNode.dataset.fadeToggleConfig || null);
const selectors = (config && config.selectors);
if (selectors) {
try {
// query both the triggering and the target element
const trigger = elmNode.querySelector(selectors.trigger || null);
let target = elmNode.querySelector(selectors.target || null);
if (trigger && target) {
// create a `FadeToggle` target type.
target = new FadeToggle(target, config.duration);
// parse a trigger node's fade-toggle configuration and state.
const triggerStates = ((
JSON.parse(trigger.dataset.fadeToggleTriggerConfig || null)
|| {}
).states || {});
// get a trigger node's state change values.
const fadeinStateValues = (triggerStates.fadein || {});
const fadeoutStateValues = (triggerStates.fadeout || {});
// get a trigger node's state change text contents.
const fadeinText = fadeinStateValues.textContent || null;
const fadeoutText = fadeoutStateValues.textContent || null;
// rerender trigger node's initial text value.
renderTargetStateDependedTriggerText(
target,
trigger,
fadeinText,
fadeoutText
);
// couple trigger and target by event listening/handling ...
trigger.addEventListener(
'click',
handleFadeToggleWithBoundContext.bind({
// ... and by binding both and some text values
// that are sensitive to state changes
// as context properties to the handler.
target,
trigger,
fadeinText,
fadeoutText
})
);
}
} catch (exception) {
console.warn(exception.message, exception);
}
}
}
function initializeEveryFadeToggle() {
// get list of all elements that contain a fade-toggle configuration
const configContainerList = document.querySelectorAll('[data-fade-toggle-config]');
// do initialization for each container separately.
configContainerList.forEach(initializeFadeToggle);
}
initializeEveryFadeToggle();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"},"duration":3}'>
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button" data-fade-toggle-trigger-config='{"states":{"fadeout":{"textContent":"Hide Heading"},"fadein":{"textContent":"Show Heading"}}}'>Toggle Heading</button>
</div>
<div class="main" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"}}'>
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Toggle Heading</button>
</div>
This is happening because document.querySelector(".button") only returns the first element with class .button (reference).
You might want to try document.querySelectorAll(".button") (reference) to add your event listeners.
(Though this will only toggle your first heading - for the very same reason. ;))
I want the div element to get the class of "showtext" when you scroll 100 pixels or less above the element. When you're 100 pixels or more above it, it has the class of "hidden".
I am trying to use a ref to access the div element, and use a method called showText to check and see when we scroll to 100 pixels or less above that div element, i'm using scrollTop for this.
Then i use componentDidMount to add a window event listener of scroll, and call my showText method.
I am new to this, so I am sure there is mistakes here and probably bad code. But any help is appreciated!
import React, {Component} from 'react';
class SlideIn extends Component{
state={
showTexts: false,
}
showText=()=>{
const node= this.showTextRef;
if(node.scollTop<=100)
this.setState({
showTexts: true
})
}
componentDidMount(){
window.addEventListener('scroll', this.showText() )
}
render(){
const intro= document.querySelector('.intro')
return(
<div classname={this.state.showTexts ? 'showText' : 'hidden'} ref={node =>this.showTextRef = node}>
{window.addEventListener('scroll', this.showText)}
<h1>You did it!</h1>
</div>
)
}
}
export default SlideIn
I have tried using this.showText in my window scroll event, and as you see above this.showText(), neither have worked. I tried to use the current property on my div ref in my showText method, and it threw a error saying the scrollTop could not define the property of null.
Again I am new to this and have never added a window event listener this way, nor have I ever used scrollTop.
Thanks for any help!
When you attach an event listener you have to pass a function as a parameter. You are calling the function directly when you add the event listener.
In essence, you need to change:
componentDidMount(){
window.addEventListener('scroll', this.showText() )
}
to:
componentDidMount(){
window.addEventListener('scroll', this.showText)
}
In your scroll listener you should check the scroll position of the window(which is the element where you are performing the scroll):
showText = () => {
if (window.scrollY <= 100) {
this.setState({
showTexts: true
});
}
}
Also, you are attaching the event listener in the render method. The render method should only contain logic to render the elements.
Pass function as parameter like
window.addEventListener('scroll', this.showText)
and remove it from return.
Then you just need to do only this in function
if(window.scrollY<=100)
this.setState({
showTexts: true
})
use your div position here
You need to use getBoundingCLientRect() to get scroll position.
window.addEventListener("scroll", this.showText); you need to pass this.showText instead of calling it.
classname has speeling mistake.
showText = () => {
const node = this.showTextRef;
const {
y = 0
} = (node && node.getBoundingClientRect()) || {};
this.setState({
showTexts: y <= 100
});
};
componentDidMount() {
window.addEventListener("scroll", this.showText);
}
render() {
const intro = document.querySelector(".intro");
return (
<div
className={this.state.showTexts ? "showText" : "hidden"}
ref={node => (this.showTextRef = node)}
>
<h1>You did it!</h1>
</div>
);
}
condesandbox of working example: https://codesandbox.io/s/intelligent-shannon-1p6sp
I've put together a working sample for you to reference, here's the link: https://codesandbox.io/embed/summer-forest-cksfh
There are few things to point out here in your code:
componentDidMount(){
window.addEventListener('scroll', this.showText() )
}
Just like mgracia has mentioned, using this.showText() means you're directly calling the function. The right way is just to use this.showText.
In showText function, the idea is you have to get how far user has scrolled from the top position of document. As it was called using:
const top = window.pageYOffset || document.documentElement.scrollTop;
now it's safe to check for your logic and set state according to the value you want, here I have put it like this:
this.setState({
showTexts: top <= 100
})
In your componentDidMount, you have to call showText once to trigger the first time page loading, otherwise when you reload the page it won't trigger the function.
Hope this help
Full code:
class SlideIn extends Component {
state = {
showTexts: false,
}
showText = () => {
// get how many px we've scrolled
const top = window.pageYOffset || document.documentElement.scrollTop;
this.setState({
showTexts: top <= 100
})
}
componentDidMount() {
window.addEventListener('scroll', this.showText)
this.showText();
}
render() {
return (
<div className={`box ${this.state.showTexts ? 'visible' : 'hidden'}`}
ref={node => this.showTextRef = node}>
<h1>You did it!</h1>
</div>
)
}
}
.App {
font-family: sans-serif;
text-align: center;
height: 2500px;
}
.box {
width: 100px;
height: 100px;
background: blue;
position: fixed;
left: 10px;
top: 10px;
z-index: 10;
}
.visible {
display: block;
}
.hidden {
display: none;
}