I want to have a resizeable modal only on height so I did write some code but while trying to grow it to the bottom because it's going fast and out of the element it doesn't have any impact, also I have seen codes like this but they work properly like this I don't know what I'm missing.
also, I want to ask; is it the right way of doing resizeable components in react? I did try to write it with states but I faced some problems like it was growing unexpectedly.
import React, { FC, useCallback, useMemo, useRef } from "react";
import { PrimitivesT } from "../Table/Table";
interface ModalProps {
children: JSX.Element | PrimitivesT;
display: boolean;
width: string;
height: string;
x?: number;
y?: number;
boxShadow?: boolean;
}
const Modal: FC<ModalProps> = ({
children,
display = false,
// initial height
height = "0",
width = "0",
x,
y,
boxShadow = true,
}) => {
const ref = useRef<HTMLDivElement>(null);
const styles = useMemo<React.CSSProperties>(
() => ({
display: display ? "block" : "none",
height: height,
width,
minHeight: "15px",
position: "absolute",
left: x,
top: y,
boxShadow: boxShadow ? "1px 1px 10px 5px var(--gray)" : undefined,
borderRadius: "5px",
backgroundColor: "white",
zIndex: 900,
}),
[display, height, width, x, y, boxShadow]
);
const bottomStyle = useMemo<React.CSSProperties>(
() => ({
cursor: "row-resize",
width: "100%",
position: "absolute",
bottom: "0",
left: "0",
height: "5px",
}),
[]
);
const onMouseDown =
useCallback((): React.MouseEventHandler<HTMLDivElement> => {
let y = 0;
let h = 60;
const onMouseMove = (e: MouseEvent) => {
const YDir = e.clientY - y;
if (ref.current) ref.current.style.height = `${h + YDir}px`;
};
const onMouseUp = () => {
try {
ref.current?.removeEventListener("mousemove", onMouseMove);
ref.current?.removeEventListener("mouseup", onMouseUp);
} catch (err) {
console.error(err);
}
};
return e => {
e.stopPropagation();
const bounding = ref.current?.getBoundingClientRect();
if (bounding?.height) h = bounding?.height;
y = e.clientY;
ref.current?.addEventListener("mousemove", onMouseMove);
ref.current?.addEventListener("mouseup", onMouseUp);
};
}, []);
return (
<div
ref={ref}
style={styles}
data-testid="Modal"
onMouseDown={e => e.stopPropagation()}>
{children}
<div style={bottomStyle} onMouseDown={onMouseDown()}></div>
</div>
);
};
export default Modal;
I think it didn't work that way because it's modal and it has to be fixed or absolute so I change the element that I was attaching event listeners instead of the resizeable target I used document object.
const onMouseDown =
useCallback((): React.MouseEventHandler<HTMLDivElement> => {
let y = 0;
let h = 60;
const onMouseMove = (e: MouseEvent) => {
const YDir = e.clientY - y;
if (ref.current) ref.current.style.height = `${h + YDir}px`;
};
const onMouseUp = () => {
try {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
} catch (err) {
console.error(err);
}
};
return e => {
e.stopPropagation();
const bounding = ref.current?.getBoundingClientRect();
if (bounding?.height) h = bounding?.height;
y = e.clientY;
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
};
}, []);
Related
I have been trying to create a little animation on canvas using React.js.
I expect the red block inside the canvas to go up and drop back down when I hit my spacebar by decreasing the velocity so that it will execute the if statement I have got and pull it back down, but so far it's not working. I can see the velocity value did change and it did execute the if statement when I console.log, it doesn't seem like showing on the animation.
const { useRef, useEffect, useState } = React;
const Canvas = () => {
const { innerWidth: innerwidth, innerHeight: innerheight } = window;
const contextRef = useRef()
const [position, setPosition] = useState({ x: 100, y: 100 })
const [size, setSize] = useState({ width: 30, height: 30 })
const [velocity, setVelocity] = useState({ x: 0, y: 0 })
const gravity = 4.5
const draw = (context) => {
context.fillStyle = 'red'
context.fillRect(position.x, position.y, size.width, size.height);
};
const update = (context, canvas) => {
draw(context)
setPosition({ x:position.x, y:position.y += velocity.y })
if (position.y + size.height +velocity.y <= canvas.height) {
setVelocity({ x:velocity.x, y: velocity.y += gravity })
} else {
setVelocity({ x:velocity.x, y: velocity.y = 0 })
}
}
const animate = (context, width, height, canvas) => {
requestAnimationFrame(() => {
animate(context, width, height, canvas )
})
context.clearRect(0, 0, width, height)
update(context, canvas)
}
useEffect(() => {
const canvas = contextRef.current;
const context = canvas.getContext("2d");
canvas.width = innerwidth - 2
canvas.height = innerheight - 2
animate(context, canvas.width, canvas.height, canvas)
}, []);
const handleKeydown = (e) => {
switch(e.keyCode) {
case 37:
console.log(velocity)
return "left";
case 39:
return "right";
case 32:
setVelocity({ x:velocity.x, y:velocity.y -= 50 });
console.log(velocity)
break
default:
console.log("keyCode is " + e.keyCode)
return 'default';
}
}
return (
<canvas ref={contextRef} tabIndex={-1} onKeyDown={(e) => {
handleKeydown(e)
}} />
);
};
const App = () => <Canvas />;
ReactDOM
.createRoot(document.getElementById("root"))
.render(<App />);
html, body { width: 100%; height: 100%; margin: 0; }
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
What can I try next?
You should wrap some of your function in a useCallback so that they are not redefined on each render.
Also, I would use key instead of keyCode.
const { useCallback, useEffect, useState, useRef } = React;
const gravity = 4.5;
const Canvas = () => {
const { innerWidth: innerwidth, innerHeight: innerheight } = window;
const canvasRef = useRef();
const [position, setPosition] = useState({ x: 100, y: 100 });
const [size, setSize] = useState({ width: 30, height: 30 });
const [velocity, setVelocity] = useState({ x: 0, y: 0 });
const draw = useCallback((context) => {
context.fillStyle = 'red';
context.fillRect(position.x, position.y, size.width, size.height);
}, [position, size]);
const update = useCallback((context, canvas) => {
draw(context)
setPosition({ x: position.x, y: position.y += velocity.y })
if (position.y + size.height + velocity.y <= canvas.height) {
setVelocity({ x: velocity.x, y: velocity.y += gravity })
} else {
setVelocity({ x:velocity.x, y: velocity.y = 0 })
}
}, [position, size, velocity]);
const animate = (context, width, height, canvas) => {
requestAnimationFrame(() => {
animate(context, width, height, canvas);
});
context.clearRect(0, 0, width, height);
update(context, canvas);
};
useEffect(() => {
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
canvas.width= innerwidth - 10;
canvas.height= innerheight - 10;
animate(context, canvas.width, canvas.height, canvas)
}, []);
const handleKeyDown = useCallback(({ key }) => {
switch (key) {
case 'ArrowLeft':
break;
case 'ArrowRight':
break;
case ' ': // Spacebar
setVelocity({ x: velocity.x, y: velocity.y -= 50 });
console.log(velocity)
break
default:
console.log(`Unknown key: ${key}`);
}
}, []);
return (
<canvas
ref={canvasRef}
tabIndex={-1}
onKeyDown={handleKeyDown}
/>
);
};
const App = () => <Canvas />;
ReactDOM
.createRoot(document.getElementById("root"))
.render(<App />);
html, body, #root { width: 100%; height: 100%; margin: 0; }
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Below is the code(not very elegant or complete, but working) for the dragging functionality in react version 17. Dragging the element works from left to right, but not from right to left. Can anyone please advise the issue or is something is missing.
import React, { useState, useEffect } from "react";
const Draggables = (props) => {
const [dragging, setDragging] = useState(false);
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
if (dragging) {
document.addEventListener("mousemove", { onMouseMove });
document.addEventListener("mouseup", { onMouseUp });
} else if (!dragging) {
document.removeEventListener("mousemove", { onMouseMove });
document.removeEventListener("mouseup", { onMouseUp });
}
}, [dragging]);
const mouseDown = (e) => {
// only left mouse button
if (e.button !== 0) return;
setDragging(true);
e.stopPropagation();
e.preventDefault();
};
const onMouseUp = (e) => {
setDragging(false);
e.stopPropagation();
e.preventDefault();
};
const onMouseMove = (e) => {
if (!dragging) return;
setPos({
x: e.pageX,
});
e.stopPropagation();
e.preventDefault();
};
return (
<div className="draggable"
onMouseDown={mouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
style={{
position: "absolute",
left: `${pos.x}px`,
top: 50 + "px",
}}
>
D
</div>
);
};
export default Draggables;
Below is the corresponding CSS.
.draggable {
cursor: pointer;
width: 4px;
height: 4px;
border-radius: 50px;
background-color: blue;
}
Thanks
I have a grid with data, and when I click on a draggable icon I want to take every selected element and store it inside an array. Currently, my code works, but for some reason currently, not all selected elements are saved in the array, some get stored others not in a random way. Why is this occurring and how can I push each item once its selected in the array? Here is my code:
import * as React from 'react';
import { ReorderContext } from './main';
import { useDraggable, useDroppable } from '#progress/kendo-react-common';
export const DraggableRow = (props) => {
const [dropped, setDropped] = React.useState(false);
const [dragged, setDragged] = React.useState(false);
const [direction, setDirection] = React.useState(null);
const [selectedItems] = React.useState([]);
const [initial, setInitial] = React.useState({
x: 0,
y: 0,
});
const { dragStart, reorder } = React.useContext(ReorderContext);
const element = React.useRef(null);
const handlePress = (event) => {
if (event.ctrlKey == true) {
element.current.style.color = 'red';
selectedItems.push(props.dataItem);
console.log(selectedItems);
}
setInitial({
x: event.clientX - event.offsetX,
y: event.clientY - event.offsetY,
});
};
const handleDragStart = (event) => {
if (
!event.originalEvent.target ||
!event.originalEvent.target.dataset.dragHandle
) {
return;
}
setDragged(true);
dragStart(props.dataItem);
};
const handleDrag = (event) => {
if (!element.current || !dragged) {
return;
}
element.current.style.transform = `translateY(${
event.clientY - initial.y + event.scrollY
}px)`;
};
const handleDragEnd = () => {
setDragged(false);
setDropped(false);
setInitial({
x: 0,
y: 0,
});
};
const handleRelease = () => {
if (!element.current) {
return;
}
element.current.style.transform = null;
};
const handleDragEnter = () => {
setDropped(true);
setDirection(null);
};
const handleDragOver = (event) => {
if (!element.current) {
return;
}
const rect = element.current.getBoundingClientRect();
setDirection(
rect.top + rect.height / 2 <= event.pageY ? 'after' : 'before'
);
};
const handleDragLeave = () => {
setDropped(false);
setDirection(null);
};
const handleDrop = () => {
reorder(props.dataItem, direction);
setDropped(false);
setDirection(null);
};
useDraggable(
element,
{
onPress: handlePress,
onDragStart: handleDragStart,
onDrag: handleDrag,
onDragEnd: handleDragEnd,
onRelease: handleRelease,
},
{
autoScroll: dragged,
}
);
useDroppable(element, {
onDragEnter: handleDragEnter,
onDragOver: handleDragOver,
onDragLeave: handleDragLeave,
onDrop: handleDrop,
});
return (
<React.Fragment>
{dropped && direction === 'before' && (
<tr
style={{
outlineStyle: 'solid',
outlineWidth: 1,
outlineColor: 'red',
}}
/>
)}
<tr
{...props.elementProps}
ref={element}
style={{
backgroundColor: '#fff',
userSelect: 'none',
pointerEvents: dragged ? 'none' : undefined,
opacity: dragged ? '0.8' : undefined,
}}
/>
{dropped && direction === 'after' && (
<tr
style={{
outlineStyle: 'solid',
outlineWidth: 1,
outlineColor: 'red',
}}
/>
)}
</React.Fragment>
);
};
and here is a runnable example:
https://stackblitz.com/edit/react-cv2hnu-euptwm?file=app/draggable-row.jsx
I am using this react code for getting a custom cursor following the main cursor. animation is done using gsap and lerp function. But the animation is not seamless and the chrome performance monitor shows the CPU usage is passing 100%. pls help me figure out this problem. I have referred to this video link for getting the cursor animation: https://www.youtube.com/watch?v=MEO6yQLAgKw&list=PLtSHrBhMos7hXeImRWnnC38mdYp_4c333&index=2&t=630s
import gsap from 'gsap';
import React, {Component} from 'react';
import './cursor.scss';
class Cursor extends Component{
constructor(props){
super(props);
this.state={
x : 0,
y : 0
};
this.cursor = React.createRef();
this.cursorConfigs = {
x: { previous: 0, current: 0, amt: 0.2 },
y: { previous: 0, current: 0, amt: 0.2 },
};
this.lerp = (a, b, n) => (1 - n) * a + n * b;
}
componentDidMount(){
window.addEventListener("mousemove", e=> {
this.setState({
x: e.pageX,
y:e.pageY
})
});
this.cursor.current.style.opacity = 0;
this.onMouseMoveEv = () => {
this.cursorConfigs.x.previous = this.cursorConfigs.x.current = this.state.x;
this.cursorConfigs.y.previous = this.cursorConfigs.y.current = this.state.y;
gsap.to(this.cursor.current,{
duration: 1,
ease: "Power4.easeOut",
opacity: 1,
});
window.removeEventListener("mousemove", this.onMouseMoveEv);
requestAnimationFrame(() =>this.render());
};
window.addEventListener("mousemove", this.onMouseMoveEv);
}
render(){
this.cursorConfigs.x.current = this.state.x;
this.cursorConfigs.y.current = this.state.y;
for (const Key in this.cursorConfigs){
this.cursorConfigs[Key].previous = this.lerp(
this.cursorConfigs[Key].previous,
this.cursorConfigs[Key].current,
this.cursorConfigs[Key].amt
);
}
console.log(this.cursorConfigs.x.previous, this.cursorConfigs.x.current)
var styles = {
transform:`translateX(${this.cursorConfigs.x.previous}px) translateY(${this.cursorConfigs.y.previous}px)`
}
requestAnimationFrame(() =>this.render());
return(
<div className="cursor" ref={this.cursor} style={styles}>
<div className="cursor-media">
</div>
</div>
)
}
}
export default Cursor;```
You actualy don't need to update on every mousemove. Consider debounce setState
example:
// delay in ms
function debounced(delay, fn) {
let timerId;
return function (...args) {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
fn(...args);
timerId = null;
}, delay);
};
}
function handler(e) {
this.setState({
x: e.pageX,
y: e.pageY,
});
};
window.addEventListener("mousemove", debounced(200, handler));
I am trying to create a draggable preact component. My current implementation breaks if the cursor moves to fast. Here is the code.
export { Draggable };
import { h } from "preact";
import { useState } from "preact/hooks";
const Draggable = (props: any) => {
const [styles, setStyles] = useState({});
const [diffPos, setDiffPos] = useState({ diffX: 0, diffY: 0 });
const [isDragging, setIsDragging] = useState(false);
const dragStart = (e: MouseEvent): void => {
const boundingRect =
(e.currentTarget as HTMLElement).getBoundingClientRect();
setDiffPos({
diffX: e.screenX - boundingRect.left,
diffY: e.screenY - boundingRect.top,
});
setIsDragging(true);
}
const dragging = (e: MouseEvent): void => {
if (isDragging === true) {
const left = e.screenX - diffPos.diffX;
const top = e.screenY - diffPos.diffY;
setStyles({ left: left, top: top });
}
}
const dragEnd = (): void => {
setIsDragging(false);
}
return (
<div
class="draggable"
style={{ ...styles, position: "absolute" }}
onMouseDown={dragStart}
onMouseMove={dragging}
onMouseUp={dragEnd}
>
{props.children}
</div>
);
}
I tried to fix it by creating a mouseup event listener but the element stops dragging if I move the mouse to fast.
Here is my attempted fix:
export { Draggable };
import { h } from "preact";
import { useState } from "preact/hooks";
const Draggable = (props: any) => {
const [styles, setStyles] = useState({});
const [diffPos, setDiffPos] = useState({ diffX: 0, diffY: 0 });
const [isDragging, setIsDragging] = useState(false);
const dragStart = (e: MouseEvent): void => {
const boundingRect =
(e.currentTarget as HTMLElement).getBoundingClientRect();
setDiffPos({
diffX: e.screenX - boundingRect.left,
diffY: e.screenY - boundingRect.top,
});
setIsDragging(true);
// ------------------------------------------------------------ Added an event listener
document.addEventListener("mouseup", dragEnd, { once: true });
}
const dragging = (e: MouseEvent): void => {
if (isDragging === true) {
const left = e.screenX - diffPos.diffX;
const top = e.screenY - diffPos.diffY;
setStyles({ left: left, top: top });
}
}
const dragEnd = (): void => {
setIsDragging(false);
}
return (
<div
class="draggable"
style={{ ...styles, position: "absolute" }}
onMouseDown={dragStart}
onMouseMove={dragging}
// -------------------------------------------------------- Removed onMouseUp
>
{props.children}
</div>
);
}
The problem is that onMouseMove() triggers every time you move the mouse, so, if you move over 200 pixels very slowly, that's 200 iterations. Try instead using onDragStart and onDragEnd. Full working demo.
Ultimately, your change will be here in render()...
return (
<div
class="draggable"
style={{ ...styles, position: "absolute" }}
onDragStart={(e) => dragStart(e)}
onDragEnd={(e) => dragging(e)}
draggable={true}
>
{props.children}
</div>
);
I use dragEnd(), so only two events actually fire with dragging: start and end. MouseMove was being fired every time there was movement, which could be hundreds of times during dragging.
Also, by giving it that extra draggable={true} param, the browser will treat it as a naturally-draggable item (i.e., a semi-opaque version of the dragged item will visually appear at the position of the cursor, as the user drags the element around).
Finally, to speed up things just a smidge, I removed the eventListener you had for dragEnd in dragStart().