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;
}
Related
So, my problem is that I have a component, I associated an animation to it and it is working when the component is rendered for the first time, but on an event click I change some conditions and some props associated to this component, But my element is not re rendered, it is just changing what has been changed, that means that the element is not removed from the dom et added to the DOM again, that's why I am not able to see the animation again, so it is not re-rendered or I just did not get what re render means.
I tried some solutions of course, but I am stuck, I tried to use this method :
this.forceUpdate();
But again, I am still not getting anything.
I dont think I have to write the whole code I wrote, becuase it is a lot and includes many other things but This is what I think is needed.
methodWillReceiveProps in my component :
componentWillReceiveProps(props) {
if (props.isRerendered) {
this.forceUpdate();
}
}
props.isRendered is returning true everytime, I checked with some console.log methods.
This is what is rendered :
render() {
return (
<div
className={cs({
"tls-forms": true,
"tls-forms--large": this.props.type === "S",
"tls-forms--medium tls-forms--login": !(this.props.type === "S")
})}
>
// content here
</div>);
}
And here is the sass file and the simple fading animation :
.tls-forms {
animation: formFading 3s;
// childs properties here
}
#keyframes formFading {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
I will really appreciate any help given.
You could make use of keys that react is using to determine whether something has changed. This means that your render method should look something like this:
import shortid from "shortid";
getRandomKey = () => {
return shortid.generate();
}
render() {
return (
<div
key={this.getRandomKey()}
className={cs({
"tls-forms": true,
"tls-forms--large": this.props.type === "S",
"tls-forms--medium tls-forms--login": !(this.props.type === "S")
})}
>
// content here
</div>);
}
Since you need to run animation on each render, you'll need to generate some random key every time (that's why we are calling this.getRandomKey() on each render). You can use whatever you like for your getRandomKey implementation, though shortid is pretty good for generating unique keys.
One way of animating a component is to attach a CSS class to it. But, when animation is done, you have to detach the CSS class so that you can re-attach when you want to animate again.
Here is a basic example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
animateFlag: false
};
}
componentDidUpdate() {
if (this.state.animateFlag) {
setTimeout(() => {
this.setState({ animateFlag: false });
}, 3000);
}
}
render() {
return (
<div className="App">
<button
onClick={() =>
this.setState({ animateFlag: !this.state.animateFlag })
}
>
{this.state.animateFlag ? "Wait" : "Re-animate"}
</button>
<div className={this.state.animateFlag ? "text animate" : "text"}>
Hello CodeSandbox
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.text {
font-size: 40px;
}
.text.animate {
animation: formFading 3s;
}
#keyframes formFading {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Note that, I am setting animateFlag to false in ComponentDidUpdate, so that when I click the Re-animate button again, I can re-attach the animate class to the div element.
I set timeout duration to 3000ms because, the animation takes 3000ms.
I have a simple component which looks like this:
import React from "react";
import './MyContainer.css';
class MyContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
showWhereClicked = (e) => {
console.log(`you have clicked X:${e.screenX} Y:${e.screenY}`);
// do stuff
}
render() {
return (
<div className="myContainer" onClick={this.showWhereClicked}>
I am 500px tall.
</div>
);
}
}
export default MyContainer;
Whenever I click anywhere inside <MyContainer />, I get a console message giving me an X and Y coordinate of where I clicked on screen.
I wish to place a <div> inside at the X and Y location of my mouse click. Ideally a box or something, say 100x100px wide.
Later I wish to implement a way for me to freely move these <div> components around the screen.
How can I achieve this?
The way I would handle this is by using css in js.
You can set the position of any DOM-Element with position: absolute;, top : yCoordinate and left : xCoordinate css attributes.
// take control over the style of a component
const [style, setStyle] = useState(initialStyle);
const setCoordinates = (x,y) => {
// You don't need whitespace in here, I added it for readability
// I would recommend using something like EmotionJS for this
return `position:absolute;
left:${x}px;
top:${y}px;`
}
...
return(
<div
style = {style}
onClick = { e => {
const newStyle =
setCoordinates(e.target.screenX,
e.target.screenY);
setStyle(newStyle);
}}
></div>)
You can then set those in any shape or form and the desired result should be visible. You won't need to redraw anything, because the DOM didn't change, just the css.
class MyContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
placedDiv:{
top:-9999px;
left:-9999px; // hide div first
width:100px;
height:100px;
position:absolute;
}
};
}
showWhereClicked = (e) => {
console.log(`you have clicked X:${e.screenX} Y:${e.screenY}`);
this.setState({
placedDiv:{
top:e.screenY + 'px'
left:e.screenX + 'px'
}
})
// do stuff
}
render() {
return (
<div className="myContainer" onClick={this.showWhereClicked}>
I am 500px tall.
<div style={this.state.placedDiv}></div>
</div>
);
}
}
export default MyContainer;
.myContainer {
position:relative /// in CSS!!!
}
In brief,
I have a infinite scroll list who render for each Item 5 PureComponent.
My idea is to somehow, only render the 5 PureComponent if the Item is visible.
The question is,
How to detect if the Item component is visible for the user or not?
Easiest solution:
add scrollPosition and containerSize to this.state
create ref to container in render()
<div ref={cont => { this.scrollContainer = cont; }} />
in componentDidMount() subscribe to scroll event
this.scrollContainer.addEventListener('scroll', this.handleScroll)
in componentWillUnmount() unsubscribe
this.scrollContainer.removeEventListener('scroll', this.handleScroll)
your handleScroll should look sth like
handleScroll (e) {
const { target: { scrollTop, clientHeight } } = e;
this.setState(state => ({...state, scrollPosition: scrollTop, containerSize: clientHeight}))
}
and then in your render function just check which element should be displayed and render correct ones numOfElementsToRender = state.containerSize / elementSize and firstElementIndex = state.scrollPosition / elementSize - 1
when you have all this just render your list of elements and apply filter base on element's index or however you want to sort them
Ofc you need to handle all edge cases and add bufor for smooth scrolling (20% of height should be fine)
You can use the IntersectionObserver API with a polyfill (it's chrome 61+) . It's a more performant way (in new browsers) to look for intersections, and in other cases, it falls back to piro's answer. They also let you specify a threshold at which the intersection becomes true. Check this out:
https://github.com/researchgate/react-intersection-observer
import React from 'react';
import 'intersection-observer'; // optional polyfill
import Observer from '#researchgate/react-intersection-observer';
class ExampleComponent extends React.Component {
handleIntersection(event) {
console.log(event.isIntersecting); // true if it gets cut off
}
render() {
const options = {
onChange: this.handleIntersection,
root: "#scrolling-container",
rootMargin: "0% 0% -25%"
};
return (
<div id="scrolling-container" style={{ overflow: 'scroll', height: 100 }}>
<Observer {...options}>
<div>
I am the target element
</div>
</Observer>
</div>
);
}
}
I have a React component, which visibility and position can be changed by the user.
The visibility by adding and removing a CSS class, the position by a function, which sets the new position after Drag & Drop as top and left.
That works, but my problem is that React does not update the style (and therefore does not rewrite the position to initial), when the component gets rerendered for the visiblity.
class MoveableCard extends React.Component {
...
render() {
...
return <div className={(this.props.isVisible ? '' : 'hide')}
draggable="true" onDragStart={dragStart}
style={{top:'initial', left:'initial'}}>
...
</div>
}
}
function dragStart(event) {
var style = window.getComputedStyle(event.target, null)
event.dataTransfer.setData("text/plain", JSON.stringify({
id:event.target.getAttribute('data-reactid'),
x:(parseInt(style.getPropertyValue("left"),10) - event.clientX),
y:(parseInt(style.getPropertyValue("top"),10) - event.clientY)
}))
}
function dragOver(event) {
event.preventDefault()
return false
}
function drop(event) {
let data = JSON.parse(event.dataTransfer.getData("text/plain"))
let el = document.querySelectorAll("[data-reactid='" + data.id + "']")[0]
el.style.left = (event.clientX + parseInt(data.x, 10)) + 'px'
el.style.top = (event.clientY + parseInt(data.y, 10)) + 'px'
event.preventDefault()
return false
}
document.body.addEventListener('dragover',dragOver,false)
document.body.addEventListener('drop',drop,false)
When the Card is rendered for the first time, the style looks like style="top: initial; left: initial;".
When the Card gets moved, the style looks like style="top: 162px; left: 320px;".
When the Card is closed, the class hide gets added, but the style remains style="top: 162px; left: 320px;", no matter, how I create the style object.
So, how can I force React to update the style?
Or is there another way to accomplish this?
Short version of the answer:
Use inner state and the component lifecycle
Long version:
First of all, I would recommend putting your event handlers inside the component instead of global methods:
class MoveableCard extends React.Component {
dragStart(event) {}
dragOver(event) {}
drop(event) {}
}
Secondly, in the constructor of the component, bind the this-context of the component to those handlers. (Well that, or you use arrow functions inside the render method)
constructor() {
this.dragStart = this.dragStart.bind(this);
this.dragOver = this.dragOver.bind(this);
this.drop = this.drop.bind(this);
}
In order to let the component 'update' or re-render, I would recommend mutating its inner state in this case. Therefore, you first add an initial value in the initial state within componentWillMount.
componentWillMount() {
this.state = { top: 0, left: 0 };
}
Within the event handlers, you can now update top and left on the innerState, using this.setState (and that's what you needed to bind this for).
drop() {
// Assuming you filled in this.left and this.top in the dragOver method
this.setState({ top: this.top, left: this.left });
}
By using the setState, a re-render will be triggered with the new values on the inner state. Now, you can use this.state.top and this.state.left in your render method:
render() {
return (
<div className={(this.props.isVisible ? '' : 'hide')}
draggable="true"
onDragStart={this.dragStart}
style={{top: this.state.top, left: this.state.left}}>
</div>
);
}
Okay, got a solution based on Andrew, dejakob and Chris answers, so thank you all :)
I thought initially, I could not move the Functions into the Component, because the Drop Event with the final position was emitted by the element, where I drop my Card, not by the Card itself.
But there is a dragend Event, which is emitted by the Card and contains the position.
Now I only had to use this to set the position in the state (and remove it via a ref to unsetPosition in the parent).
class MoveableCard extends React.Component {
constructor(props) {
super(props)
this.state = {
styles: {top:'initial', left:'initial'}
}
this.drop = this.drop.bind(this);
}
dragStart(e) {
let style = window.getComputedStyle(e.target, null)
this.setState({l: parseInt(style.getPropertyValue("left")) - e.clientX, y: parseInt(style.getPropertyValue("top")) - e.clientY})
}
drop(e) {
this.setState({left: this.state.l + e.clientX, top: this.state.y + e.clientY})
e.preventDefault()
return false
}
unsetPosition() {
this.setState({styles: {top:'initial', left:'initial'}})
}
render() {
return <div className={(this.props.isVisible ? '' : 'hide')}
draggable="true"
onDragStart={this.dragStart}
onDragEnd={this.drop}
style={this.state.styles}>
...
</div>
}
}
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