Drag Background in React - javascript

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

Related

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>

React - Render happening before state is changed

Very confused about this one. From the code, If you move the green square you will see the printed values.
I expected all of the code to run before the state is changed and the rerender however from this code example i can see the rerender happens as soon as the function to change state is called.
import React, { useState, useEffect, useRef } from "react";
import styled, { ThemeProvider, css } from "styled-components";
export default function App() {
const pos = useRef({ x: 0, y: 0 });
const offset = useRef({ x: 0, y: 0 });
const myDiv = useRef(null);
const [rerender, setRerender] = useState(0);
console.log("render");
useEffect(() => {
window.addEventListener("mousemove", doSomething);
window.addEventListener("mousedown", doSomethingAgain);
return () => {
window.removeEventListener("mousemove", doSomething);
window.removeEventListener("mousedown", doSomethingAgain);
};
}, [rerender]);
console.log("global", rerender);
function doSomething(e) {
if (e.buttons == 1) {
const newPos = {
x: e.pageX,
y: e.pageY
};
pos.current = newPos;
console.log("first", rerender);
setRerender(rerender + 1);
console.log("second", rerender);
}
}
function doSomethingAgain(e) {
const offsetX = e.pageX - myDiv.current.offsetLeft;
const offsetY = e.pageY - myDiv.current.offsetTop;
offset.current = { x: offsetX, y: offsetY };
}
return (
<div className="App">
<DIV ref={myDiv} off={offset.current} pos={pos.current}></DIV>
</div>
);
}
const DIV = styled.div`
display: inline-block;
width: 100px;
height: 100px;
background-color: green;
position: absolute;
top: ${({ pos, off }) => pos.y - off.y}px;
left: ${({ pos, off }) => pos.x - off.x}px;
`;

moving an svg element on a page within a container

I have a project that lets you place a text element onto an SVG. Once that element is placed, I then want it to be draggable so that it can be repositioned. currently, I can place the text element, and I can do edit it, however, when I go to move it, that is where I am having problems. Currently, the issue is that when I click and move a text element, it moves. But when I release and click again, the element will move on its own always to the same spot. When I go to click it, then it will move off the page entirely.
I have looked and attempted several different ideas including these sources:
https://dev.to/tvanantwerp/dragging-svgs-with-react-38h6
http://www.petercollingridge.co.uk/tutorials/svg/interactive/dragging/
I believe, it has to do with the fact that a mouseclick reads according to the whole page, but I need it to be a specific area.
This is my canvas element that has the SVG:
import React, { useState } from "react";
const SvgCanvas = (props) => {
const [Dragging, setDragging] = useState(false);
const [origin, setOrigin] = useState({ x: 0, y: 0 });
const [coordinates, setCoordinates] = useState({ x: 20, y: 20 });
const DragClick = (e) => {
console.log(e.target.attributes);
e.preventDefault();
setDragging(true);
setOrigin({ x: e.clientX, y: e.clientY });
};
const DragMove = (e) => {
e.preventDefault();
if (Dragging) {
// var x = parseFloat(e.target.getAttributeNS(null,"x"))
// console.log(x)
// e.target.setAttributeNS(null,"x",x+.1);
console.log("Origin= X: "+origin.x+", Y: "+origin.y);
console.log("Current= X: "+e.clientX+", Y: "+e.clientY);
setCoordinates({ x: e.clientX - origin.x, y: e.clientY = origin.y });
}
};
const DragRelease = (e) => {
e.preventDefault();
setDragging(false);
};
var lnk = props.details.url;
const TextBoxArray = [];
for (var i = 0; i < props.details.box_count; i++) {
//console.log("for");
//console.log(props.Meme[i]);
const y = '20';
const x = '30';
TextBoxArray.push(
<text
key={i}
id={"MemeBox" + i}
//y={`${coordinates.y}`}
// x={`${coordinates.x}`}
transform={`translate(${coordinates.x}, ${coordinates.y})`}
fontSize={props.Meme[i] ? props.Meme[i].range : "16"}
fill={props.Meme[i] ? props.Meme[i].color : "black"}
onMouseDown={(e) => DragClick(e)}
onMouseMove={(e) => DragMove(e)}
onMouseUp={(e) => DragRelease(e)}
>
{props.Meme[i]
? (document.getElementById("MemeBox" + i).textContent =
props.Meme[i].text)
: null}
</text>
);
}
return (
<div id="SvgCanvas">
<svg>
<image key={props.details.id} x="10" y="10" href={lnk} />
{TextBoxArray}
</svg>
</div>
);
};
export default SvgCanvas;
And, the app.js file
import React, { useState} from "react";
import ReactDom from 'react-dom';
import SvgControls from "./components/svgcontrols";
import SvgCanvas from "./components/svgcanvas";
import MemeChooser from "./components/selector";
import "./style/meme.css";
function App() {
const [Meme, setMeme] = useState([]);
const [MemeText, setMemeText] = useState([]);
const MemeSet = (e) => {
setMeme(e);
};
const TextSet = (MemeEditArray, InitialArraySetFlag) => {
if (!InitialArraySetFlag) {
setMemeText(MemeEditArray);
}
if (InitialArraySetFlag) {
var MemeEditArrayCopy = [...MemeText];
MemeEditArrayCopy[MemeEditArray["id"]] = MemeEditArray;
setMemeText(MemeEditArrayCopy);
}
};
return (
<div className="App">
<header>Meme Maker</header>
<div id="MemeMaker">
<SvgCanvas Meme={MemeText} details={Meme} />
<SvgControls getter={MemeText} setter={TextSet} details={Meme} Create={document.getElementsByName(SvgCanvas)} />
<MemeChooser click={MemeSet} />
{
//console.log(ReactDom.findDOMNode(this).querySelector(SvgCanvas))
}
</div>
</div>
);
}
export default App;
I appreciate any suggestions, that you may have to help me fix this.
So after a while, I was able to come up with a solution for my issue. What I did was add a starting variable, and then making adjustments off of that.
const DragMove = (e) => {
e.preventDefault();
if (Dragging) {
// var x = parseFloat(e.target.getAttributeNS(null,"x"))
// console.log(x)
// e.target.setAttributeNS(null,"x",x+.1);
console.log("Origin= X: "+origin.x+", Y: "+origin.y);
console.log("Current= X: "+e.clientX+", Y: "+e.clientY);
setCoordinates({ x: e.clientX - origin.x, y: e.clientY = origin.y });
}
};
The problem was that when I was chaning the coordinates based on the click position, I was not adjusting for the initial position of the text.
const [Dragging, setDragging] = useState(false);
const [origin, setOrigin] = useState({ x: 0, y: 0 });
const [coordinates, setCoordinates] = useState({ x: 20, y: 20 });
const DragClick = (e) => {
console.log(e.target.attributes);
e.preventDefault();
setDragging(true);
setOrigin({ x: e.clientX, y: e.clientY });
};
const DragMove = (e) => {
e.preventDefault();
var startX = coordinates.x;
var startY = coordinates.y;
if (Dragging) {
setCoordinates({
x: startX + e.clientX - origin.x,
y: startY + e.clientY - origin.y,
});
}
};

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