I'm using useGuesture and to make some elements draggable in R3F.
When I start dragging (or even click the element) it moves to the center of the screen before it is then possible to drag it around.
How can I get it to drag from it's current position?
This is some code I've got from https://maxrohde.com/2019/10/19/creating-a-draggable-shape-with-react-three-fiber/ and tried to get it to work with objects that do not start in center of screen.
export default function Draggable(props) {
const [position, setPosition] = useState(props.position);
const { size, viewport } = useThree();
const aspect = size.width / viewport.width;
const bind: any = useGesture(
{
onDrag: ({ event, offset: [x, y] }) => {
const [, , z] = position;
setPosition([x / aspect, -y / aspect, z]);
},
onDragEnd: event => {
props.onMove(position);
},
onClick: props.onClick,
},
{ drag: { filterTaps: true } },
);
if (!props.children) return null;
return (
<mesh position={position} {...bind()}>
{props.children}
</mesh>
);
}
I know this is a bit old now but I was having the same issue. I've not used 'aspect' in mine as my use is a lot simpler, and used direction instead of offset - offset sends it flying off the page:
const bind = useDrag(({ down, direction: [x, y] }) => {
if (down) {
setPosition((prev) => [prev[0] + x, prev[1] - y, prev[2]]);
}
});
Related
I've been working on a project for fun where I want to have a couple of different eyes bouncing around a screen. I want the pupil of the eye to follow the user's mouse as it moves around the page. In order to accomplish this, I planned to add an eventlistener for mousemove and update state every time the position is updated.
However, I have found that the state update triggers a re-run of my useEffect for drawing the canvas. This causes the appearance that the ball moves faster when the user is moving their mouse. I was wondering if anyone has any advice on how to avoid this or how to better organize my code to get around this issue?
You can use the "Console" tab in the sandbox to see log statements that occur during unintended updates.
const draw = (ctx: CanvasRenderingContext2D, mousePos: {x: number, y: number}) => {
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
bubbles.forEach((bubble, index) => {
bubble.intersectWall();
bubble.move();
bubble.draw(mousePos);
});
};
const handleMouseMove = (e: MouseEvent) => {
setMousePos({ x: e.screenX, y: e.screenY });
};
// Initialize bubbles on page load
useEffect(() => {
const canvas = canvasRef.current as HTMLCanvasElement;
const context = canvas.getContext("2d") as CanvasRenderingContext2D;
window.addEventListener("mousemove", handleMouseMove);
const initBubbles = [];
for (let i = 0; i < 1; i++) {
initBubbles.push(
new Bubble(
randomNumber(0 + BUBBLE_RADIUS, window.innerWidth - BUBBLE_RADIUS),
randomNumber(0 + BUBBLE_RADIUS, window.innerHeight - BUBBLE_RADIUS),
randomNumber(-1, 4),
randomNumber(-4, 4),
context
)
);
}
setBubbles(initBubbles);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, []);
useEffect(() => {
const canvas = canvasRef.current as HTMLCanvasElement;
const context = canvas.getContext("2d") as CanvasRenderingContext2D;
let animationFrameId: number;
const render = () => {
draw(context, mousePos);
animationFrameId = window.requestAnimationFrame(render);
};
render();
return () => {
window.cancelAnimationFrame(animationFrameId);
};
}, [draw]);
Codepen example: https://codepen.io/pietrofan12/pen/wvEBPPx
Thank you in advance and sorry for bad english
I was about to make some rotation programm (worked by mouse click & drag) but the 'removeEventListener' doesn't work.
can u explain me how to work it and why doesn't it work?
And this is my first question in here so if u find any problems about this question, I'll gladly accept it.
<body>
<div class="wrap">
<div class="target">target</div>
</div>
</body>
const html = document.querySelector("html");
const info = document.querySelector(".info");
const target = document.querySelector(".target");
const wrap = document.querySelector(".wrap");
let center = {
x: target.getBoundingClientRect().left + target.clientWidth / 2,
y: target.getBoundingClientRect().top + target.clientHeight / 2,
};
window.addEventListener("resize", () => {
center = {
x: target.getBoundingClientRect().left + target.clientWidth / 2,
y: target.getBoundingClientRect().top + target.clientHeight / 2,
};
});
const rotate = function () {
target.addEventListener("mousemove", (e) => {
const x = center.x - e.clientX;
const y = center.y - e.clientY;
const radian = Math.atan2(y, x);
const degree = ((radian * 180) / Math.PI).toFixed(0);
target.style.transform = "rotate(" + degree + "deg)";
});
};
target.addEventListener("mousedown", rotate, true);
target.addEventListener("mouseup", () => {
target.removeEventListener("mousedown", rotate, false);
});
I've tried to change this part. target -> wrap and removeEventListener's param to another one. But none of those worked
target.addEventListener("mouseup", () => {
target.removeEventListener("mousedown", rotate, false);
});
The capture/ use capture falg must match to remove listener. Since you used true for your mousedown even listener you also must use true to remove event listener:
target.addEventListener("mouseup", () => {
target.removeEventListener("mousedown", rotate, true);
});
See docs for more details: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener#matching_event_listeners_for_removal
I'm studying react, but there's something I can't understand.
Where does this code get the mouse position value?
function useMousePosition() {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const setPosition = ({ x, y }) => {
setX(x);
setY(y);
};
useLayoutEffect(() => {
window.addEventListener("mousemove", setPosition);
return () => window.removeEventListener("mousemove", setPosition);
}, []);
return [x, y];
}
It is here:
window.addEventListener("mousemove", setPosition);
and here:
const setPosition = ({ x, y }) => {
setX(x);
setY(y);
};
The first part listens for mouse movement, and when it happens, passes the data to the x and y variables of the setPosition function, the second part.
I am working on an app which the app is intented to capture/load an image, then get the correct color from the touched coordinate on that image / camera view. (Similar to iOS's Swatches app)
The libraries I am using is react-native-camera, react-native-get-pixel-color and react-native-draggable .
There is some logic issue I'm stucked on currently....and I have researched from online and still lost in the jungle
Ratio of every devices is not same so I could not have hardcoded on the ratio, based on the document stated that the ratio is only available for android but not for iOS (default will be 4:3 for android if not defined any). So I'm thinking is it better to not to set it ? Rely on pictureSize better ? Or can without both ? Any advices is welcome...
The part which I'm quite confusing now is on the calculation part, I wanted to get the correct part of the image's color, despite different ratio , device's screen size and image size:
Live view - always get from the center
Still view - based on draggable and get the coordinates
renderCamView = () => {
if (this.state.isEnabled) {
return (
<Image
source={{ uri: this.state.liveImg.data.uri }}
style={styles.imgPreview}
/>
);
} else {
return (
<RNCamera
ref={(ref) => {
this.camera = ref;
console.log('isref');
}}
onCameraReady={()=> this.getCamData()}
style={{
flex: 1,
justifyContent: 'flex-start',
alignItems: 'center',
backgroundColor: colors.black
}}
type={RNCamera.Constants.Type.back}
flashMode={this.getFlashMode()}
exposure={this.state.camera.exposure}
whiteBalance={this.state.camera.whiteBalance}
//ratio={'1:1'}
// pictureSize={this.state.pictureSize}
captureAudio={false}
androidCameraPermissionOptions={{
title: 'Permission to use camera',
message: 'We need your permission to use your camera',
buttonPositive: 'Ok',
buttonNegative: 'Cancel',
}}
/>
);
}
};
renderCenterView = () => {
if(this.state.isEnabled){
const { liveImg, mainView } = this.state;
return <Draggable
x={(this.state.mainView.width/2)-20} y={(this.state.mainView.height/2)-20}
isCircle
onShortPressRelease={this.captureColor}
onDragRelease={this.onDragEvt}
minX={0}
minY={0}
maxX={this.state.mainView.width}
maxY={this.state.mainView.height}
>
<View
style={{ width:50, height:50,borderWidth:15, borderRadius: 50, borderColor:'#d1c1b6', opacity: 0.8 }}></View>
</Draggable>
}else{
return <TouchableOpacity
onPress={this.captureColor}
style={[styles.middlePoint, { top:(this.state.mainView.height/2)-20, left:(this.state.mainView.width/2)-20 }]}/>
}
}
// Logic on Draggable, to find the coordinates on image view
onDragEvt = (event, gestureState, bounds) => {
let sourceWidth = this.state.liveImg.width;
let sourceHeight = this.state.liveImg.height;
// Find view dimensions
let width = this.state.mainView.width;
let height = this.state.mainView.height;
// Assuming image has been scaled to the smaller dimension to fit, calculate the scale
let wScale = width / sourceWidth;
let hScale = height / sourceHeight;
let scale = Math.min(wScale, hScale);
// Find the offset in the direction the image doesn't fill the screen
// For ImageViews that wrap the content, both offsets will be 0 so this is redundant
let offsetX = 0;
let offsetY = 0;
if (wScale <= hScale) {
offsetY = height / 2 - (scale * sourceHeight) / 2;
} else {
offsetX = width / 2 - (scale * sourceWidth) / 2;
}
// Convert event coordinates to image coordinates
let sourceX = (gestureState.moveX - offsetX) / scale;
let sourceY = (gestureState.moveY - offsetY) / scale;
if (sourceX < 0) {
sourceX = 0;
} else if (sourceX > sourceWidth) {
sourceX = sourceWidth - 5;
}
if (sourceY < 0) {
sourceY = 0;
} else if (sourceY > sourceHeight) {
sourceY = sourceHeight - 5;
}
this.setState({ dragView: { x: sourceX, y: sourceY } });
};
getPixelByPercentage(percentage, widthOrHeight) {
return (percentage / 100) * widthOrHeight;
}
getPixel(imageData, x, y) {
try {
GetPixelColor.setImage(isIOS ? imageData.uri : imageData.base64).catch(
(err) => {
// Handle errors
console.error(err);
},
);
GetPixelColor.pickColorAt(x, y)
.then((color) => {
// from react-native-get-pixel-color
// 'color' return as HEX
})
.catch((err) => {
// Handle errors
console.error(err);
});
} catch (e) {}
}
captureColor = async () => {
if (this.state.isEnabled) {
// live view
const { dragView, liveImg } = this.state;
if (Object.keys(dragView).length !== 0) {
let sourceX = dragView.x;
let sourceY = dragView.y;
this.getPixel(liveImg.data, sourceX, sourceY);
} else {
let getPiX = this.getPixelByPercentage(50, liveImg.width);
let getPiY = this.getPixelByPercentage(50, liveImg.height);
this.getPixel(liveImg.data, getPiX, getPiY);
}
} else {
if (this.camera) {
const data = await this.camera.takePictureAsync(CAM_OPTIONS);
await Image.getSize(data.uri, (width, height) => {
console.log('W: ', width);
console.log('H: ', height);
this.setState({
capImage: {
data: data,
imageBase64: data.base64,
width: width,
height: height,
},
});
}).then((res) => {
const { capImage } = this.state;
let getPiX = this.getPixelByPercentage(50, capImage.width);
let getPiY = this.getPixelByPercentage(50, capImage.height);
this.getPixel(capImage.data, getPiX, getPiY);
});
}
}
};
NOTES:
1. renderCamView() - render either Camera or Image
2. renderCenterView() - render the center item, either is draggable in 'still' view or fixed touchable button in 'live' view to always get the center point
3. onDragEvt() - the Draggable's event to keep track the draggable item's movement
4. this.state.mainView - the state that holds width and height of the root <View /> of this screen
5. captureColor() - onPress event to get the pixel color
6. this.state.isEnabled - define it is still view / live view
Live view - (Camera view)
Still view - (Capture and display as image / image select from album)
*Sorry for my bad English, if there is any confusing part please let me know
I am trying to move a div with the help of mousemove event.
Here is the code for the same.
https://codepen.io/anurag92/pen/VEoQOZ
class ImageMarker extends React.Component {
constructor(props) {
super(props);
this.mouseDown = this.mouseDown.bind(this);
this.mouseUp = this.mouseUp.bind(this);
this.mouseMove = this.mouseMove.bind(this);
this.paint = this.paint.bind(this);
}
mouseDown(e) {
const position = {
left: this.marker.offsetLeft,
top: this.marker.offsetTop
};
this.hitOffset = {
x: e.pageX - position.left,
y: e.pageY - position.top,
diameter: this.diameter(),
markerRadius: 10
};
this.marker.addEventListener('mousemove', this.mouseMove);
this.marker.addEventListener('mouseup', this.mouseUp);
this.marker.addEventListener('mouseleave', this.mouseUp);
e.preventDefault();
}
mouseMove(e) {
this.position = {
x: e.pageX - this.hitOffset.x,
y: e.pageY - this.hitOffset.y
};
this.position.x = Math.round(this.position.x);
this.position.y = Math.round(this.position.y);
this.position.x = Math.min(700 - 1, Math.max(0, this.position.x));
this.position.y = Math.min(700 - 1, Math.max(0, this.position.y));
this.paint();
}
mouseUp(e) {
this.marker.removeEventListener('mousemove', this.mouseMove);
this.marker.removeEventListener('mouseup', this.mouseUp);
this.marker.removeEventListener('mouseleave', this.mouseUp);
}
diameter() {
return 1;
}
paint() {
if (JSON.stringify(this.paintedPosition) !== JSON.stringify(this.position)) {
this.paintedPosition = Object.assign({}, this.position);
}
if (this.position) {
this.marker.style.left = `${100 * this.position.x / 700}%`;
this.marker.style.top = `${100 * this.position.y / 700}%`;
}
return this;
}
render() {
this.position = this.position || {
x: 5,
y: 5
};
this.offset = 0;
return <div className='outer'
ref = {ref => {
this.canvasRef = ref;
}}
>
<div className = 'marker'
onMouseDown = {event => this.mouseDown(event)}
ref = {ref => {
this.marker = ref;
}} >
</div>
</div>;
}
}
// export default ImageMarker;
ReactDOM.render(<ImageMarker />,
document.getElementById('root')
);
When i move my cursor slowly its working fine, but on fast movement mouseleave gets triggered and as a result div is not able to keep up with the cursor.
Can someone please tell me a potential fix for this.
You could resolve that by attaching the mouseMove (and mouseUp)to the whole document This way they will be fired no matter if the mouse gets out of the element you want to drag.
Just remember to detach the event during componentWillUnmount to avoid leaks.
Further more if you want you site to work on mobile you need to attach touch, pointer or drag events. See the code of the kendo-draggable abstraction for a reference. We are using it in our react components. Bind to the element on ComponentDidMount and detach on ComponentWillUnmount