Place <div> at x,y coordinate in React - javascript

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!!!
}

Related

Make img follow curser when over element REACT

I am trying to create an effect similar to the one found here: https://gruev.space/about. When you hover over certain words, an image appears and follows your curser. My implementation will be a bit different though, as i don't need to see the characters under the image when it appears. I'm simply attempting to make an image pop up and follow my cursor when I hover over certain words in a paragraph. I'm trying to create a react component that can hold a paragraph with certain highlighted words that are "hoverable".
Below is what I have tried so far. I found the "pointer-events: none" property to be critically important so that the onMouseEnter and onMouseLeave events don't get triggered over and over when the image showes up and then subsiquently removes itself.
What I need help with is figuring out how to embed these "hoverable" words into a paragraph.
import React from "react";
import "../../index.css";
import codingJoke from "../../images/codingJoke.png";
import "./raisedTextContainer.css";
export default class RaisedTextContainer extends React.Component {
constructor(props) {
super(props);
this.state = { mouseOver: false };
this.showImageWhenHover = this.showImageWhenHover.bind(this);
this._onMouseMove = this._onMouseMove.bind(this);
}
componentDidMount() {
this.imageUpdateInterval = setInterval(this.showImageWhenHover, 10);
}
componentWillUnmount() {
clearInterval(this.imageUpdateInterval);
}
showImageWhenHover(e) {
console.log(this.state);
if (this.state.mouseOver) {
let x = this.state.mouseX;
let y = this.state.mouseY;
const image = document.querySelector(".hoverimage");
image.style.left = x - 300 + "px";
image.style.top = y - 150 + image.style.height / 2 + "px";
console.log(image.style.left, image.style.top);
//this.forceUpdate();
}
}
_onMouseMove(e) {
this.setState({ mouseX: e.clientX, mouseY: e.clientY });
}
render() {
return (
<div className="grid-item rounded raised">
<div
style={{ "z-index": 2 }}
className="photo-hover"
onMouseMove={this._onMouseMove}
onMouseEnter={() => {
this.setState({ mouseOver: true });
const image = document.querySelector(".hoverimage");
image.style.display = "block";
}}
onMouseLeave={() => {
this.setState({ mouseOver: false });
const image = document.querySelector(".hoverimage");
image.style.display = "none";
}}
>
<h3>test Image hover thing</h3>
</div>
<div class="hoverimage">
<img src={codingJoke} />
</div>
<p>{this.props.paragraph}</p>
</div>
);
}
}
.hoverimage {
position: absolute;
display: none;
pointer-events: none;
}

referring to a div from outside class

I have a div which is scrollable and i want to refer that div from outside my class. For example
const myDiv = document.getElementById('scrollDiv');
class Test extends React.Component{
listenScrollEvent = (e) => {
console.log("myDiv returns undefined",myDiv);
};
render(){
return (
<div id="scrollDiv" onScroll={this.listenScrollEvent.bind(this)}></div>
)
}
}
Here inside listenScrollEvent i want to access myDiv which is referred to div with id scrollDiv. But i'm getting a undefined value in my console log. I can use const myDiv = document.getElementById('scrollDiv'); inside my listenScrollEvent method but then every time i scroll, referring to div happens.
You can access an element by using refs in React like this -
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.ref = null;
}
listenScrollEvent = () => {
console.log(this.myRef.getBoundingClientRect().top);
};
render() {
return (
<button
ref={my => (this.myRef = my)}
id="scrollDiv"
onScroll={this.listenScrollEvent}
>
Click Me!
</button>
);
}
}
More on refs - https://reactjs.org/docs/refs-and-the-dom.html

How to detect hidden component in React

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>
);
}
}

React does not update style on rerender

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>
}
}

Get cursor position in a sibling component

Suppose I have two components:
class App extends Component {
insertToStory = (word) => {
// how to get the cursor position here?
}
render() {
return (
<div>
<StoryTextarea text={this.props.text} />
<Toolbar insert={this.insertToStory} />
</div>
)
)
}
the StoryTextarea contains a textarea, and Toolbar contains a button, when clicked, we should insert some word to the textarea under the currrent cursor position. but how can I get the cursor position in insertToStory? or is there other ways to implement this?
Using refs is a good option to achieve that.
1º Add a new method in your StoryTextArea component to get the cursor position.
class StoryTextArea extends React.Component{
constructor(props) {
super(props);
this.getCursorPosition = this.getCursorPosition.bind(this);
}
getCursorPosition(){
return this.refs.textarea.selectionStart;
}
render(){
return <div>
<textarea ref="textarea"/>
</div>
}
}
2º Add a ref to the StoryTextArea component
<StoryTextarea ref="storyTextArea" text={this.props.text} />
3º Call getCursorPosition using this.refs.storyTextArea.getCursorPosition()
insertToStory = (word) => {
let position = this.refs.storyTextArea.getCursorPosition();
}
jsfiddle example

Categories