Rendering element to DOM based on dynamic sibling positions - javascript

I have a series of <StrategyComponent> elements that are rendered through a button click and are fully draggable on the page. Currently the intial positions are static and all components will be rendered to the same position through the initialPos prop. However, since the user is free to move these wherever they want, I want to render each new component that the user adds a certain distance to the right of the right most <StrategyComponent> at the time (wherever that may be). How can I access this component or what is the best way to keep track of the right most bound in which to place incoming components.
Any help with this would be great. I'm sure it can be done, but I'm just getting used to React. Thanks!
StrategyComponent.js
class StrategyComponent extends Component {
constructor(props) {
super(props);
// creates a reference to wrapper div
this.wrapper = React.createRef();
}
state = {
pos: this.props.initialPos,
dragging: false,
rel: null, // position relative to the cursor
};
componentDidUpdate = (props, state) => {
if (this.state.dragging && !state.dragging) {
document.addEventListener("mousemove", this.onMouseMove);
document.addEventListener("mouseup", this.onMouseUp);
} else if (!this.state.dragging && state.dragging) {
document.removeEventListener("mousemove", this.onMouseMove);
document.removeEventListener("mouseup", this.onMouseUp);
}
};
// calculate relative position to the mouse and set dragging=true
onMouseDown = (e) => {
// only left mouse button
if (e.button !== 0) return;
var pos = this.wrapper.current.getBoundingClientRect();
this.setState({
dragging: true,
rel: {
x: e.pageX - pos.x,
y: e.pageY - pos.y,
},
});
e.stopPropagation();
e.preventDefault();
};
onMouseUp = (e) => {
this.setState({ dragging: false });
e.stopPropagation();
e.preventDefault();
};
onMouseMove = (e) => {
if (!this.state.dragging) return;
this.setState({
pos: {
x: e.pageX - this.state.rel.x,
y: e.pageY - this.state.rel.y,
},
});
this.props.handleParentResize(this.wrapper.current.getBoundingClientRect());
e.stopPropagation();
e.preventDefault();
};
render() {
return (
<div
ref={this.wrapper}
onMouseDown={this.onMouseDown}
draggable
className="component-wrapper"
style={{ left: this.state.pos.x + "px", top: this.state.pos.y + "px" }}
>
<h3> Strategy Component {this.props.name}</h3>
</div>
);
}
}
App.js
class App extends Component {
state = {
numComponents: 1,
};
addStrategyComponent = () => {
this.setState({ numComponents: this.state.numComponents + 1 });
};
render() {
const components = [];
for (var i = 0; i < this.state.numComponents; i++) {
components.push(
<StrategyComponent key={i.toString()} name={i.toString()} initialPos={{ x: 200, y: 200 }} />
);
}
return (
<div className="App">
<button onClick={this.addStrategyComponent}>+</button>
<div className="components-wrapper">{components}</div>
</div>
);
}
}

Related

JS/React Drawing Application with TouchMoveEvent

I am trying to build a react app and I need one component for simple handwriting and drawing on mobile/touch devices.
The problem im am facing is that the TouchMoveEvent is not fired upon small movements. Therefore it gets pretty hard if someone has a small handwriting. Sometimes some parts of letters or numbers are missing because the touchmoveevent was not fired and subsequently no line has been drawed.
Has anyone an idea how to lower the treshhold to fire the touchmoveevent or has someone a different approach to this? The goal is just a very simple mobile drawing app but the touchmoveevent is not sufficient for very detailed and small drawings as it is not fired upon small movements.
This is my code so far:
import React from "react";
const Immutable = require('immutable');
class DrawArea extends React.Component {
constructor(sized) {
super();
this.state = {
lines: new Immutable.List(),
isDrawing: false
};
console.log(sized)
this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleMouseUp = this.handleMouseUp.bind(this);
this.handleTouchStart = this.handleTouchStart.bind(this);
this.handleTouchMove = this.handleTouchMove.bind(this);
this.handleTouchEnd = this.handleTouchEnd.bind(this);
}
componentDidMount() {
document.addEventListener("mouseup", this.handleMouseUp);
document.addEventListener("touchend", this.handleTouchEnd);
}
componentWillUnmount() {
document.removeEventListener("mouseup", this.handleMouseUp);
document.removeEventListener("touchend", this.handleTouchEnd);
}
handleMouseDown(mouseEvent) {
if (mouseEvent.button != 0) {
return;
}
const point = this.relativeCoordinatesForEvent(mouseEvent);
this.setState(prevState => ({
lines: prevState.lines.push(new Immutable.List([point])),
isDrawing: true
}));
}
handleMouseMove(mouseEvent) {
if (!this.state.isDrawing) {
return;
}
const point = this.relativeCoordinatesForEvent(mouseEvent);
this.setState(prevState => ({
lines: prevState.lines.updateIn([prevState.lines.size - 1], line => line.push(point))
}));
}
handleMouseUp() {
this.setState({ isDrawing: false });
}
handleTouchStart(e) {
console.log("s")
let touch = e.touches[0];
const point = this.relativeCoordinatesForEvent(touch);
this.setState(prevState => ({
lines: prevState.lines.push(new Immutable.List([point])),
isDrawing: true
}));
}
handleTouchMove(e) {
console.log("m")
if (!this.state.isDrawing) {
return;
}
let touch = e.touches[0];
const point = this.relativeCoordinatesForEvent(touch)
this.setState(prevState => ({
lines: prevState.lines.updateIn([prevState.lines.size - 1], line => line.push(point))
}));
}
handleTouchEnd() {
console.log("e")
this.setState({ isDrawing: false });
}
relativeCoordinatesForEvent(mouseEvent) {
const boundingRect = this.refs.drawArea.getBoundingClientRect();
return new Immutable.Map({
x: mouseEvent.clientX - boundingRect.left,
y: mouseEvent.clientY - boundingRect.top,
});
}
relativeCoordinatesForTouchEvent(mouseEvent) {
const boundingRect = this.refs.drawArea.getBoundingClientRect();
return new Immutable.Map({
x: mouseEvent.clientX - boundingRect.left,
y: mouseEvent.clientY - boundingRect.top,
});
}
render() {
//console.log(this.state.lines)
//this.state.lines.map(s => console.log(s))
return (
<div
className="drawArea"
ref="drawArea"
onMouseDown={this.handleMouseDown}
onMouseMove={this.handleMouseMove}
onTouchStart={this.handleTouchStart}
onTouchMove={this.handleTouchMove}
onTouch
>
<Drawing sized={this.props.sized} lines={this.state.lines} />
</div>
);
}
}
function Drawing({ lines, sized }) {
return (
<svg className="drawing">
{lines.map((line, index) => (
<DrawingLine key={index} sized={sized} line={line} />
))}
</svg>
);
}
function DrawingLine({ line, sized }) {
let multi = sized ? 1.0 : 0.5;
const pathData = "M " +
line
.map(p => {
return `${p.get('x')*multi} ${p.get('y')*multi}`;
})
.join(" L ");
return <path className="path" d={pathData} />;
}
export default DrawArea;
ยดยดยด
I solved the problem with this thread:
javascript, is there a way to set a (smaller) threshold on touchmove event
using the PointerMoveEvent

draggable component in react not working as expected

I am trying to make a draggable button using react.
The button drags over the page in a proper manner but when I drop it. Its top and left values become negative(not even reset to their original top:0,left:0) i.e. the component goes out of the page.
code sand box link : code
main draggable.js component:
import React, { Component } from 'react';
import { Button } from '#material-ui/core';
class DraggableButton extends Component {
constructor() {
super();
this.state = {
dragging: false,
diffX: 0,
diffY: 0,
style: {
top: 0,
left: 0
}
}
}
handleMouseDown = (event) => {
console.log("element caught");
this.setState({
diffX: event.clientX - event.currentTarget.getBoundingClientRect().left,
diffY: event.clientY - event.currentTarget.getBoundingClientRect().top,
dragging: true
})
}
handleMouseMove = (event) => {
if (this.state.dragging) {
console.log("dragging");
let left = event.clientX - this.state.diffX;
let top = event.clientY - this.state.diffY;
this.setState({
style: {
left,
top
}
}, console.log("style ", this.state.style))
}
}
handleMouseUp = () => {
console.log('element released');
console.log('left value ', this.state.style.left);
console.log('top value ', this.state.style.top);
this.setState({
dragging: false,
})
}
render() {
return (
<Button
variant="contained" color="primary"
style={{ position: "absolute", ...this.state.style }}
draggable={true}
onDragStart={this.handleMouseDown}
onDrag={this.handleMouseMove}
onDragEnd={this.handleMouseUp}
// onMouseDown={this.handleMouseDown}
// onMouseMove={this.handleMouseMove}
// onMouseUp={this.handleMouseUp}
>
draggable button
</Button>
);
}
}
export default DraggableButton;
console screenshot :
As is visible in the image above at the time of dragging top: 193 left : 309 and as we dropped the element it turned to left: -109 top: -13.
why is this happening how can we fix it ?
In your handleMouseMove you need to check if event.clientX is a positive integer and then change the state, or else it will reduce the diffX value and it will be nevative. (On drag release this becomes 0)
let left = event.clientX - this.state.diffX;
handleMouseMove = (event) => {
if (this.state.dragging) {
let left = event.clientX - this.state.diffX;
let top = event.clientY - this.state.diffY;
if (event.clientX !== 0)
this.setState({
style: {
left,
top
}
});
}
};

How Can I convert React.createclass to Class Component?

I have taken an example of dragging certain div in react js from here
http://jsfiddle.net/Af9Jt/2/
Now it is in createClass and I need to convert it into class Draggable extends React.Component in order to export it into another component. Here is code
APP.JS
import React from 'react';
import './App.css';
import Draggable from './Draggable.js';
function App() {
return (
<React.Fragment>
<Draggable />
</React.Fragment>
);
}
export default App;
Draggable.js
import React from 'react';
export class Draggable extends React.Component{
constructor(props) {
super(props);
this.state = {
pos: {x: 0, y: 0},
dragging: false,
rel: null
};
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
}
// we could get away with not having this (and just having the listeners on
// our div), but then the experience would be possibly be janky. If there's
// anything w/ a higher z-index that gets in the way, then you're toast,
// etc.
// componentDidUpdate(props, state) {
// if (this.state.dragging && !state.dragging) {
// document.addEventListener('mousemove', this.onMouseMove)
// document.addEventListener('mouseup', this.onMouseUp)
// } else if (!this.state.dragging && state.dragging) {
// document.removeEventListener('mousemove', this.onMouseMove)
// document.removeEventListener('mouseup', this.onMouseUp)
// }
// }
// calculate relative position to the mouse and set dragging=true
onMouseDown(e) {
console.log("1")
console.log(this.state);
if (e.button !== 0) return
this.setState({
dragging: true,
rel: {
x: e.pageX - e.nativeEvent.offsetX,
y: e.pageY - e.nativeEvent.offsetY
}
})
e.stopPropagation()
e.preventDefault()
}
onMouseUp(e) {
this.setState({dragging: false})
e.stopPropagation()
e.preventDefault()
}
onMouseMove(e) {
if (!this.state.dragging) return
this.setState({
pos: {
x: e.pageX - this.state.rel.x,
y: e.pageY - this.state.rel.y
}
})
e.stopPropagation()
e.preventDefault()
}
render() {
return(
<div
style={{position: "absolute", left: "175px", top: "65px", border: "2px solid rgb(170, 170, 85)", padding: "10px"}}
className="my-draggable" data-reactid=".r[2zxee]" id="messi"
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
onMouseDown={this.onMouseDown}
initialPos = {{x:0,y:0}}
>
Drag Me! See how children are passed through to the div!
</div>
)
}
}
export default Draggable;
Everything runs fine in this code the box is shown but I cannot drag the div, I couldn't figure out what issue is this. How Can I Solve this?
Here is my sample code in jsfiddle
https://jsfiddle.net/6vdurk79/3/
There were a few things I noticed when converting this into a React.Component:
You never used the this.state.pos when rendering, so even if the position changed in the variables, it wouldn't move the div. The style attribute of the <div> is just hard-coded with { left: "175px", top: "65px" }
You didn't properly get the position of the mouse in your this.onMouseDown function, which caused it to forced every movement to be at the corner.
You never bound this.onMouseMove to anything. Uncommenting the big chunk of commented out code fixed this.
The initialPos attribute you place inside the <div> does absolutely nothing. I converted that into a prop in the constructor.
Here's the updated JSFiddle link: https://jsfiddle.net/ogy4xd1c/3/
And I'll embed it here on StackOverflow in a snippet.
class Draggable extends React.Component {
constructor(props) {
super(props);
this.state = {
pos: props.initialPos || {
x: 0,
y: 0
},
dragging: false,
rel: null
}
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
}
// calculate relative position to the mouse and set dragging=true
onMouseDown(e) {
if (e.button !== 0) return
const de = document.documentElement;
const box = ReactDOM.findDOMNode(this).getBoundingClientRect();
const top = box.top + window.pageYOffset - de.clientTop;
const left = box.left + window.pageXOffset - de.clientLeft;
this.setState({
dragging: true,
rel: {
x: e.pageX - left,
y: e.pageY - top,
}
})
e.stopPropagation()
e.preventDefault()
}
onMouseUp(e) {
this.setState({
dragging: false
})
e.stopPropagation()
e.preventDefault()
}
onMouseMove(e) {
if (!this.state.dragging) return
this.setState({
pos: {
x: e.pageX - this.state.rel.x,
y: e.pageY - this.state.rel.y
}
})
e.stopPropagation()
e.preventDefault()
}
componentDidUpdate(props, state) {
if (this.state.dragging && !state.dragging) {
document.addEventListener('mousemove', this.onMouseMove)
document.addEventListener('mouseup', this.onMouseUp)
} else if (!this.state.dragging && state.dragging) {
document.removeEventListener('mousemove', this.onMouseMove)
document.removeEventListener('mouseup', this.onMouseUp)
}
}
render() {
return ( <div
style={{
position: "absolute",
left: this.state.pos.x,
top: this.state.pos.y,
border: "2px solid rgb(170, 170, 85)",
padding: "10px"
}}
className="my-draggable"
data-reactid=".r[2zxee]"
id="messi"
onMouseDown={this.onMouseDown}
className="my-draggable"
>
Drag Me! See how children are passed through to the div!
</div>
)
}
}
ReactDOM.render(<Draggable initialPos={{ x: 50, y: 20 }} />, document.querySelector("#root"));
.my-draggable {
cursor: pointer;
width: 200px;
height: 200px;
background-color: #cca;
}
<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>
If you want to pass in children, you can do that too with this modified version: https://jsfiddle.net/hceLjz90/
class Draggable extends React.Component {
constructor(props) {
super(props);
this.state = {
pos: props.initialPos || {
x: 0,
y: 0
},
dragging: false,
rel: null
}
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
}
// calculate relative position to the mouse and set dragging=true
onMouseDown(e) {
if (e.button !== 0) return
const de = document.documentElement;
const box = ReactDOM.findDOMNode(this).getBoundingClientRect();
const top = box.top + window.pageYOffset - de.clientTop;
const left = box.left + window.pageXOffset - de.clientLeft;
this.setState({
dragging: true,
rel: {
x: e.pageX - left,
y: e.pageY - top,
}
})
e.stopPropagation()
e.preventDefault()
}
onMouseUp(e) {
this.setState({
dragging: false
})
e.stopPropagation()
e.preventDefault()
}
onMouseMove(e) {
if (!this.state.dragging) return
this.setState({
pos: {
x: e.pageX - this.state.rel.x,
y: e.pageY - this.state.rel.y
}
})
e.stopPropagation()
e.preventDefault()
}
componentDidUpdate(props, state) {
if (this.state.dragging && !state.dragging) {
document.addEventListener('mousemove', this.onMouseMove)
document.addEventListener('mouseup', this.onMouseUp)
} else if (!this.state.dragging && state.dragging) {
document.removeEventListener('mousemove', this.onMouseMove)
document.removeEventListener('mouseup', this.onMouseUp)
}
}
render() {
return ( <div
style={{
position: "absolute",
left: this.state.pos.x,
top: this.state.pos.y,
border: "2px solid rgb(170, 170, 85)",
padding: "10px"
}}
className="my-draggable"
data-reactid=".r[2zxee]"
id="messi"
onMouseDown={this.onMouseDown}
className="my-draggable"
>
{this.props.children}
</div>
)
}
}
ReactDOM.render(<Draggable initialPos={{ x: 50, y: 20 }}>
<h1>This is a child element</h1>
<p>This is also a child element</p>
</Draggable>, document.querySelector("#root"))
.my-draggable {
cursor: pointer;
width: 200px;
height: 200px;
background-color: #cca;
}
<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>

Drag Background in React

I 'm new in React and i'm tryind to make draggable background, but no matter how hard I try, nothing happens. I found some code on jQuery, but there many advices that it's bad practice use jQuery in React.
Maybe i make something wrong.
Thanks in advance
Here's my React code
import React from "react";
import "../styles/board.css";
class Board extends React.Component {
constructor(props) {
super(props);
this.state = { mouseCliked: 0, startX: 0, startY: 0 };
}
mouseDown(e) {
this.setState({ mouseCliked: 1, startX: e.clientX, startY: e.clientY });
}
mouseUp(e) {
this.setState({ mouseCliked: 0, startX: e.clientX, startY: e.clientY });
}
mouseMove = (e) => {
let newPosY = e.clientY - this.stateY;
let newPosX = e.clientX - this.stateX;
if (this.state.mouseClicked) {
e.target.style.backgroundPositionX += newPosX;
e.target.style.backgroundPositionY += newPosY;
}
};
render() {
return (
<div
onMouseMove={this.mouseMove.bind(this)}
onMouseUp={this.mouseUp.bind(this)}
onMouseDown={this.mouseDown.bind(this)}
className="background-image"
>
</div>
);
}
}
export default Board;
CSS:
width:300px;
height: 300px;
background-size: 1000px;
background-position-x: 0;
background-position-y: 0;
background-image: url('https://images.unsplash.com/photo-1452723312111-3a7d0db0e024?crop=entropy&dpr=2&fit=crop&fm=jpg&h=750&ixjsv=2.1.0&ixlib=rb-0.3.5&q=50&w=1450.jpg');
}
I have encountered this problem and also checked jQuery - drag div css background
Finally I came up with this solution and seemed working fine.
const imageStyleInitialValue = {
backgroundImage: "",
backgroundPosition: "0 0",
backgroundSize: "0 0",
height: 0,
width: 0,
};
const [startPoint, setStartPoint] = useState({x: 0, y: 0});
const [dragging, setDragging] = useState(false);
const [imageStartPos, setImageStartPos] = useState([0, 0]);
const [imageStyles, setImageStyles] = useState<ImageStyles>(imageStyleInitialValue);
// add onMouseMove={handleDragImage} to the image component
const handleDragImage = (e) => {
if (dragging) {
const deltaX = e.clientX - startPoint.x;
const deltaY = e.clientY - startPoint.y;
setImageStyles({...imageStyles,
backgroundPosition:
`${imageStartPos[0] + deltaX} ${imageStartPos[1] + deltaY}`
})
}
};
// add onMouseDown={handleStartDragImage} to the image component
const handleStartDragImage = (e) => {
setDragging(true);
const backgroundPosArray = imageStyles.backgroundPosition.split(" ").map(value => Number(value));
setImageStartPos(backgroundPosArray);
setStartPoint({x: e.clientX, y: e.clientY});
}
// add onMouseUp={handleEndDragImage} to the top component because you want
// to set Dragging to false when the dragging ends outside of the image
const handleEndDragImage = (e) => {
setDragging(false)
};

Using initialPos from Reactjs state

I am building a React component with fixed width and resizable height. The problem is I am getting event.clientY and this.state.initialPos with unpredictable values. Here is fiddle
And here is code of React component
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import '../styles/cal-event-styles.css';
class CalEvent extends Component {
constructor(props) {
super(props);
this.state = {
isDragging: false,
height: 40,
text: this.props.text,
color: this.props.color
}
}
componentDidMount() {
ReactDOM.findDOMNode(this).addEventListener('mousemove', this.resizePanel);
ReactDOM.findDOMNode(this).addEventListener('mouseup', this.stopResize);
ReactDOM.findDOMNode(this).addEventListener('mouseleave', this.stopResize);
}
resizePanel = (event) => {
if (this.state.isDragging) {
//I tried to use event.clientY - this.state.initialPos but it doesn't work
let delta = event.clientY + this.state.height;
console.log("event.clentY " + event.clientY);
console.log("this.state.initialPos " + this.state.initialPos);
this.setState({height: delta});
}
}
stopResize = () => {
if (this.state.isDragging) {
this.setState({
isDragging: false,
});
}
// height: this.getStep(this.state.height)
}
getStep = (height) => {
return Math.floor(height / 50) * 50;
}
startResize = (event) => {
this.setState({
isDragging: true,
initialPos: event.clientX
});
}
formatText = () => {
const { text, height } = this.state;
return text.length > 10 && height <= 100 ? text.substring(0, 14) + "..." : text;
}
render(){
const {color, text, height, isDragging, initialPos } = this.state;
console.log("this.state.isDragging: " + isDragging);
if (isDragging) {
console.log("this.state.height: " + height);
console.log("this.state.initialPos: " + initialPos);
}
return(
<div className="cal-event" onMouseUp={() => this.stopResize()} style={{height: `${height}px`, background: color}}>
<div className="cal-event-tile"><p>{this.formatText()}</p></div>
<div className="resizer-height" onMouseDown={e => this.startResize(e)}></div>
</div>
)
}
}
export default CalEvent;
I think that in this line:
let delta = event.clientY + this.state.height
delta shouldn't be the sum of those values, because you resize on the Y axis and exactly the Y (in this case) should determine the new height:
I tried this in the resizePanel method and it seem to work:
this.setState({ height: event.clientY })
The problem with this approach is that, in this case, the top position of the component is almost 0, and that is why it work quite seemlessly.
If you need it to work no matter where the component is placed on the page, you have to compute delta as the difference between the top position and the event.clientY.
To do it, I would save the absolute top position of the component in the componentDidMount lifecycle method:
componentDidMount() {
const currentNode = ReactDOM.findDOMNode(this)
currentNode.addEventListener('mousemove', this.resizePanel);
currentNode.addEventListener('mouseup', this.stopResize);
currentNode.addEventListener('mouseleave', this.stopResize);
this.setState({ initialY: currentNode.getBoundingClientRect().top })
}
Then, in the event handler, the setting of the height property will be:
this.setState(prevState => ({ height: event.clientY - prevState.initialY }))

Categories