Drag examples in the docs such as this one are extremely fast and map very accurately to my finger position. I've tried copying the examples one-to-one but there is a clear lag to catch up with my finger position.
Here is a code sandbox example which has a clear lag when testing on a real device.
Information:
React Use Gesture version: (I've tried all manner of combinations of versions with no impact but this is the configuration in the codesandbox)
"react": "17.0.2",
"react-dom": "17.0.2",
"react-scripts": "4.0.0",
"react-spring": "9.1.2",
"react-use-gesture": "9.1.3"
Device: IPhone 12 pro max
OS: [e.g. iOS14.5.1]
Browser chrome
function PullRelease() {
const [{ x }, api] = useSpring(() => ({ x: 0 }));
const bind = useDrag(({ movement: [mx], down }) =>
api.start({ x: down ? mx : 0 })
);
return (
<animated.div {...bind()} style={{ padding: 40, background: "gold", x }}>
Hello World
</animated.div>
);
}
I combed through the source code of the examples. The issue was in fact with react-spring, I needed to supply immediate: true while the touch is active.
https://codesandbox.io/s/react-spring-gesture-basic-swipe-immediate-6bje9?file=/src/App.js
function PullRelease() {
const [{ x }, api] = useSpring(() => ({ x: 0 }));
const bind = useDrag(({ movement: [mx], down }) =>
api.start({ x: down ? mx : 0,
immediate: down // the key line
})
);
return (
<animated.div {...bind()} style={{ padding: 40, background: "gold", x }}>
Hello World
</animated.div>
);
}
Related
Learning react by coding, here i'm using 'react-zoom-pan-pinch' for mouse scroll wheel, so when i zoomIn using mouse scroll and its scale gets bigger than '1.311' then it should make circle smaller ("10"), here i have created demo of what i want to achieve.
https://codesandbox.io/s/react-zoom-pan-pinch-forked-svx5v6?file=/src/index.js
you can check from console when value gets bigger that circle becomes smaller,
but in my real code i'm using 'viewGenerator' (react-d3-graph) and customNode where i have that svg and circle
import { Graph } from "react-d3-graph";
const [circleR, setCircleR] = useState("50");
const zoomChange = (event: any) => {
if (event.state.scale < 1.311) {
setCircleR("50");
}
if (event.state.scale > 1.311) {
setCircleR("10");
}
console.log("event state scale ", event.state.scale);
console.log("event circleR ", circleR);
};
function CustomNode(props) {
return (
<div>
<svg viewBox="-1 -1 2 2">
<circle
cx="0"
cy="0"
r={circleR}
strokeWidth="0.1"
stroke= "black"
fill= "orange"
/>
</svg>
</div>
);
}
const default_config = {
d3_config: {
nodeHighlightBehavior: false,
staticGraphWithDragAndDrop: true,
automaticRearrangeAfterDropNode: false,
d3: { disableLinkForce: true },
height: 512,
// width: 576,
minZoom: 1,
maxZoom: 1,
node: {
color: "lightblue",
size: 500,
highlightStrokeColor: "blue",
fontSize: 20,
highlightFontSize: 22,
labelProperty: (node: default_config) =>
node.name ? node.name : node.id,
viewGenerator: (node) => (
<CustomNode node={node} focusId={null} />
),
}
};
const [config, setConfig] = useState(default_config);
<Graph
config={config.d3_config}
/>
Problem is 'r={circleR}' is just taking default value from state, and its not updating it when i'm zooming in or zooming out.
Inside my 'zoomChange ' function i have two console.logs and i can check that it gives me right values and changes between '10' and '50' (zoomChange function works fine), then what is the reason i'm not getting updated value to 'r={circleR}' ?
writing values to it like 'r="50" or r="10"' will work but i want updated values according to setState.
english is not my mother language so could be mistakes !
I've made a covid related travel map here: Where Can We Go Now
It's a static react JS website, billy basic with create-react-app. It uses amongst other things the excellent React Simple Maps (kudos to the creator) and react-tooltip.
Expected behaviour:
Locally the tooltip works fine when tested with npm run start
I move my mouse over a country and the tooltip displays some relevant information approximately where my mouse is on the map.
The Problem:
. . . once published with npm run build sort of works but strange things happen.
Please follow link to website to observe, but in short, if the sea (i.e. an area that isn't a country - so not defined by an svg in the topojson) is clicked then the tooltip is place at the top centre of the whole map itself.
Some bits of relevant code:
Index.js (snippet):
const [content, setContent] = useState("");
return (
<div><h1> Where can we go on holiday ?* </h1>
<MapChart setTooltipContent={setContent}/>
<ReactTooltip html={true}>{content}</ReactTooltip>
</div>
);
}
MapChart.js (snippet):
return (
<div>
<p><small>*Right now in the UK: {data.headlineAdviceNow} Last updated at {updateString}</small></p>
<ComposableMap
projectionConfig={{
rotate: [-40, -30, 10],
scale: 1600
}}
width={mapWidth}
height={mapHeight}
style={{ maxWidth: "100%", height: "80vh", background: "#b3b3b3", borderStyle: "solid", borderColor: "white", margin: 0.5,
borderRadius: 20 }}
>
<ZoomableGroup zoom={zoom} center={center} translateExtent={[
[0, 0],
[mapWidth, mapHeight]
]} maxZoom={15} data-tip="">
<Sphere stroke="#E4E5E6" strokeWidth={0.5} />
<Graticule stroke="#E4E5E6" strokeWidth={0.2} />
{!loading && (<Geographies geography={geoUrl}>
{({ geographies }) =>
geographies.map((geo) => {
const d = data.countries.find((s) => s.country.iso_a3 === geo.properties.ISO_A3);
return (
<Geography
key={geo.rsmKey}
geography={geo}
style={{
default: {
outline: "none",
stroke: "grey",
strokeWidth: "1",
strokeLinecap: "butt",
fill: d ? colorScale(d[attr]) : "#F5F4F6"
},
hover: {
outline: "none",
stroke: "black",
strokeWidth: "3",
strokeLinecap: "butt",
fill: d ? colorScale(d[attr]) : "#F5F4F6"
},
pressed: {
outline: "none"
}
}}
onMouseEnter={() => {
const NAME = geo.properties.NAME;
const travelIndex = d ? d["indexDescription"] : "Unknown";
//const border = d ? d["borderStatusString"] : "Unknown";
const returnStatus = d ? (fromCountry==="GBR" ? d["ukCategory"] : "TBC") : "Unknown";
const vaccinePc = d ? d.vaccineData["total_vaccinations_per_hundred"] + "%" : "NK";
const arrival = (() => {try{ return d["restrictionsFrom"][data["iomKeys"].indexOf(fromCountry)].split("-") } catch {return [4,""]}});
const arrivalLevel = rstrctLkup[arrival()[0]]["short"]
//const arvlRtns = arrival()[1].length===0 ? arrival()[1] : arrival()[1].split(",")
//var text =""
//var i;
//for (i = 0; i < arvlRtns.length; i++) {
// if (data["restrictionDefs"][arvlRtns[i]]) {
// if (i===0) {text="<br />Arrival Details:<br />"}
// text += data["restrictionDefs"][arvlRtns[i]] + "<br>";
//}}
setTooltipContent(`<b>${NAME.toUpperCase()}:</b> ${travelIndex}<br /><p style="text-align: left"><b>RISK FACTORS</b><br />Vaccinated: ${vaccinePc}<br /><br /><b>CURRENT RESTRICTIONS</b><br />Entry: ${arrivalLevel}<br />Return: ${returnStatus}</p>`);
}}
onMouseLeave={() => {
setTooltipContent("");
}}
/>
);
})
}
</Geographies>
)}
</ZoomableGroup>
</ComposableMap>
</div>
);
};
export default memo(MapChart);
What I've tried:
Variations of setting tooltip position, and moving where and what data-tip="" is. Also, banging head on wall.
I was inaccurate in my question, so didn't provide relevant details that was the source of the problem.
I publish with github actions and this was the fix:
yarn install --frozen-lockfile
The frozen lockfile was the key to make sure I was using the exact versions of relevant packages as I had been testing locally. I'd erroneously suggested I was using npm.
Problem
I don't understand how to properly zoom to a position of a click, .center(center) on projection doesn't do anything.
Code
import React, { Fragment, useState } from 'react'
import { ComposableMap, ZoomableGroup, Geographies, Geography } from 'react-simple-maps'
import { geoConicEqualArea } from 'd3-geo'
import map from '../public/Russia.json'
const VectorMap = ({ onClick, width = 990, height = 505, onBlur }) => {
const [zoom, setZoom] = useState(1)
const [center, setCenter] = useState([100, 100])
// Zoom event handlers
const onWheel = e => (e.deltaY > 0 ? setZoom(prev => prev - 0.3) : setZoom(prev => prev + 0.3))
return (
<Fragment>
<ComposableMap
projection={() =>
geoConicEqualArea()
.scale(690)
// projection doesn't change anyway
.center(center)
.parallels([40, 80])
.rotate([265])
.translate([130, 5])
}
{...{ width, height }}
style={{
width: '85vw',
height: '100vh'
}}
>
<ZoomableGroup {...{ zoom }}>
<Geographies geography={map}>
{(geographies, projection) =>
geographies.map((geography, i) => (
<Geography
key={i}
{...{ geography, onWheel, projection, onBlur }}
onClick={(obj, e) => {
onClick(obj)
setCenter(e.clientX, e.clientY)
// setZoom(1.5)
}}
style={{
default: {
fill: '#ECEFF1',
stroke: '#607D8B',
strokeWidth: 0.75,
outline: 'none'
},
hover: {
fill: '#607D8B',
stroke: '#607D8B',
strokeWidth: 0.75,
outline: 'none'
},
pressed: {
fill: '#FF5722',
stroke: '#607D8B',
strokeWidth: 0.75,
outline: 'none'
}
}}
/>
))
}
</Geographies>
</ZoomableGroup>
</ComposableMap>
<button className="ZoomBtn" onClick={() => setZoom(prev => prev + 0.1)}>
+
</button>
<button className="ZoomBtn" onClick={() => setZoom(prev => prev - 0.1)}>
-
</button>
</Fragment>
)
}
export default VectorMap
I also noticed there's a center property for ZoomableGroup but it also doesn't seem to be working properly. It zooms to one place and map becomes no draggable anymore.
Here are the warnings in console:
Warning: componentWillReceiveProps has been renamed, and is not recommended for use.
* Move data fetching code or side effects to componentDidUpdate.
* If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps.
* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.
Please update the following components: Geographies, ZoomableGroup dll_d6a88dbe3071bd165157.js:12608:15
Source map error: Error: Invalid URL: webpack://[name]_[chunkhash]/webpack/bootstrap
Resource URL: http://localhost/_next/static/development/dll/dll_d6a88dbe3071bd165157.js?ts=1577625154482
Source Map URL: dll_d6a88dbe3071bd165157.js.map
Unexpected value
translate(
NaN
NaN
)
scale(1)
translate(-495 -250)
parsing transform attribute.
Additional info
React version: 16.12
react-simple-maps version: 0.12.1
Help would be appreciated.
You can modify the value of center in ComposableMap.
Suppose if you want center to be at (20, 20), you must write:
center:[20, 20]
I am trying to run a few simple animations using react-native-animatable library. (But I believe the question should be generic to any react animations so adding other tags as well.)
The problem is, in the first time, the image animates just as expected. But when aimed to start second animation animation with the gesture, the image translation starts from its original coordinates.
A search yielt, in Android development (which is obviously not my case) there seems a method, setFillAfter which sets the coordinate after the animation.
My question is, how to set the location (left / top values for example) to the final translated point so that consecutive animation starts from the point the previous translation left.
The expo snack for below code block is here.
import * as React from 'react';
import { Image, StyleSheet, ImageBackground } from 'react-native';
import * as Animatable from 'react-native-animatable';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import testImg from './test.png';
import backImg from './back.png';
export default class App extends React.Component {
onTestMove(event) {
this.testAnimRef.transitionTo({
translateX: event.nativeEvent.translationX,
translateY: event.nativeEvent.translationY,
}, 0);
}
render() {
return (
<ImageBackground source={backImg} style={{ flex: 1 }} >
<PanGestureHandler
key={`test`}
onGestureEvent={(e) => { this.onTestMove(e) }}
onHandlerStateChange={e => { }}
>
<Animatable.View style={styles._animatable_view}
ref={((ref) => { this.testAnimRef = ref }).bind(this)}
useNativeDriver={true}
>
<Image source={testImg} style={styles._image} />
</Animatable.View>
</PanGestureHandler>
</ImageBackground>
);
}
}
const styles = StyleSheet.create({
_image: {
width: 50,
height: 25,
resizeMode: 'contain',
backgroundColor: 'black',
borderColor: 'gainsboro',
borderWidth: 2,
},
_animatable_view: {
position: "absolute",
top: 200,
left: 100,
},
});
I had the same problem trying to move around some cards in a view, and upon further dragging, they would reset to their origin.
My theory is/was that while the translated view would have its x / y coordinates translated, this would not apply to the parent of that view, and so the animated event passed from that component would initially have the original coordinates (nuke me if I'm wrong here)
So my solution was to keep an initial offset value in state, and maintain this every time the user releases the dragged motion
_onHandleGesture: any
constructor(props: OwnProps) {
super(props)
this.state = {
animationValue: new Animated.ValueXY({ x: 0, y: 0 }),
initialOffset: { x: 0, y: 0 },
}
this._onHandleGesture = (e: PanGestureHandlerGestureEvent) => {
this.state.animationValue.setValue({
x: e.nativeEvent.translationX + this.state.initialOffset.x, <- add initial offset to coordinates passed
y: e.nativeEvent.translationY + this.state.initialOffset.y,
})
}
}
_acceptCard = (cardValue: number) => {
const { targetLocation, onAccept } = this.props
const { x, y } = targetLocation
onAccept(cardValue)
Animated.spring(this.state.animationValue, {
// Some animation here
}).start(() => {
this.setState({ initialOffset: targetLocation }) // <- callback to set state value for next animation start
})
}
and the render method
<PanGestureHandler
onHandlerStateChange={this.onPanHandlerStateChange}
onGestureEvent={this._onHandleGesture}
failOffsetX={[-xThreshold, xThreshold]}
>
<Animated.View
style={{
position: "absolute",
left: 0,
top: 0,
transform: [{ translateX: this.state.animationValue.x }, { translateY: this.state.animationValue.y }],
}}
>
<CardTile size={size} content={content} layout={layout} backgroundImage={backgroundImage} shadow={shadow} />
</Animated.View>
</PanGestureHandler>
This example is based on the react-native-gesture-handler library, but the concept should apply to other solutions.
Dont know if this way is advisable, though it is functional.
Hope this helps!
I'm currently working on an HSV-Colorpicker component for a react-native project I have. I have everything setup so that I have a draggable object on the HSV-canvas and a slider next to it to handle the hue change.
However, I now want to be able to set the position of the draggable object by clicking on the hsv-canvas. I got it working so that the draggable element moves to the right position, however, when I then try to drag the element with a pan-gesture again, the offset for the pan-gesture starts from the wrong position.
Here is the corresponding parts of code:
setPanFromTouch(event) {
const x = event.nativeEvent.locationX;
const y = event.nativeEvent.locationY;
const gesture = {
moveX: x,
moveY: y
};
this.state.pan.setOffset({ x: x, y: y });
this.state.pan.setValue({ x: 0, y: 0 });
this.updateColorFromGesture((gesture as any));
}
//inside the render method those are the 2 components that respond to
//touches and pans
//Parent container for the HSV-hue and the draggable element
<TouchableWithoutFeedback
style={{
width: this.props.colorBoxWidth,
height: this.props.height
}}
onPress={this.setPanFromTouch}
>
<Animated.View
{...this.panResponder.panHandlers}
style={[this.state.pan.getLayout(), {
height: this.draggableSize,
width: this.draggableSize,
borderRadius: this.draggableSize / 2,
backgroundColor: 'transparent',
borderColor: '#fff',
borderWidth: 1
}]}>
</Animated.View>
//pan-gesture setup inside the constructor method
(this as any).panResponder = PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onStartShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
this.state.pan.setOffset({ x: this.state.panValues.x, y: this.state.panValues.y });
this.state.pan.setValue({ x: 0, y: 0 });
},
onPanResponderMove: Animated.event([null, {
dx: this.state.pan.x,
dy: this.state.pan.y
}]),
onPanResponderRelease: (e: GestureResponderEvent, gesture: PanResponderGestureState) => {
this.state.pan.flattenOffset();
this.updateColorFromGesture(gesture);
}
});
//component will mount code
componentWillMount() {
this.state.pan.addListener((c) => this.setState({
...this.state,
panValues: c
}));
}
I know that inside the onPanResponderGrant I am resetting the offset again, however this is necessary for the dragging to work correctly. I also tried to set the new values for this.state.panValues.x and this.state.panValues.y after updating the pan position from the touch event but setState does not change the behavior at all.
This is what the colorpicker looks like:
I would be really grateful if someone has an idea what I could do in this situation. Let me know if you need more code or anything else. Thanks in advance :)
I solved the issue by using a normal instance variable instead of the state for detecting if a pan position was set by a touch. Here is what I did:
//At the top of my react component
private setFromTouch = false;
//in the onPanResponderGrant method of the PanResponder.create inside
//the constructor
...
onPanResponderGrant: () => {
if (!this.setFromTouch) {
this.state.pan.setOffset({ x: this.state.panValues.x, y:
this.state.panValues.y });
} else {
this.setFromTouch = false;
}
this.state.pan.setValue({ x: 0, y: 0 });
}
...
//New setFromTouch() method
setPanFromTouch(event) {
const x = event.nativeEvent.locationX;
const y = event.nativeEvent.locationY;
const gesture = {
moveX: x,
moveY: y
};
this.state.pan.setOffset({ x: x, y: y });
this.state.pan.setValue({ x: 0, y: 0 });
this.setFromTouch = true;
this.updateColorFromGesture((gesture as any));
}