Closed felixmagnell closed 6 years ago
anybody?
You need to do two things:
Good luck! -Albert
Op 5 jul. 2017 20:19 schreef "Anthony Phillips" notifications@github.com:
anybody?
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/react-native-community/react-native-svg/issues/374#issuecomment-313184758, or mute the thread https://github.com/notifications/unsubscribe-auth/AA7_v2bzxZM2pHQzZ9WOBbLKAMY0uunWks5sK9OggaJpZM4N9EK7 .
@AlbertBrand Could you elaborate on how to do that? I've been having a ton of trouble trying to figure out how I should calculate new zoom and offset. I have the zoomScale
property from the ScrollView but what do I do with it?
I also have a
Confused as to where to go from here.
@Nexuist To zoom a viewbox, you start with an initial affine transform of the identity matrix. When you handle a pinch/zoom event, you translate the center of the touches/interaction to the origin, scale according to the change in distance between the touches, and translate back to the center, (all operations are on the transform matrix). Then just multiply this matrix with the current one, and set the new resulting matrix as the transform on the root (zoomed) element. https://github.com/d3/d3-zoom might give some inspiration/clues.
@feluxz @dk0r @AlbertBrand @Nexuist @yangyi I've made an example here: https://snack.expo.io/@msand/svg-pinch-to-pan-and-zoom
@msand Great! Any idea how to get this to work with IOS? I tried it out but couldn't move the object.
@feluxz I've updated the example. Seems the Svg element had to be wrapped in a View, and set the PanResponder on that instead. Now it works in the iOS simulator as well, at least for me ;) I don't have any iPhone available to test on. Please try again! https://snack.expo.io/@msand/svg-pinch-to-pan-and-zoom
Another with start/mid/end alignment: https://snack.expo.io/@msand/svg-pinch-to-pan-and-zoom-with-alignment
I made a render-prop version of it and published it as a npm package called zoomable-svg https://www.npmjs.com/package/zoomable-svg Example: https://snack.expo.io/@msand/zoomablesvg-render-prop
import React, { Component } from 'react';
import { View, StyleSheet, Dimensions } from 'react-native';
import { Svg } from 'expo';
import ZoomableSvg from 'zoomable-svg'; // 1.0.0
const { G, Circle, Path, Rect } = Svg;
const { width, height } = Dimensions.get('window');
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<ZoomableSvg
align="mid"
width={width}
height={height}
viewBoxSize={65}
svgRoot={({ transform }) => (
<Svg
width={width}
height={height}
viewBox="0 0 65 65"
preserveAspectRatio="xMinYMin meet">
<G transform={transform}>
<Rect x="0" y="0" width="65" height="65" fill="white" />
<Circle cx="32" cy="32" r="4.167" fill="blue" />
<Path
d="M55.192 27.87l-5.825-1.092a17.98 17.98 0 0 0-1.392-3.37l3.37-4.928c.312-.456.248-1.142-.143-1.532l-4.155-4.156c-.39-.39-1.076-.454-1.532-.143l-4.928 3.37a18.023 18.023 0 0 0-3.473-1.42l-1.086-5.793c-.103-.543-.632-.983-1.185-.983h-5.877c-.553 0-1.082.44-1.185.983l-1.096 5.85a17.96 17.96 0 0 0-3.334 1.393l-4.866-3.33c-.456-.31-1.142-.247-1.532.144l-4.156 4.156c-.39.39-.454 1.076-.143 1.532l3.35 4.896a18.055 18.055 0 0 0-1.37 3.33L8.807 27.87c-.542.103-.982.632-.982 1.185v5.877c0 .553.44 1.082.982 1.185l5.82 1.09a18.013 18.013 0 0 0 1.4 3.4l-3.31 4.842c-.313.455-.25 1.14.142 1.53l4.155 4.157c.39.39 1.076.454 1.532.143l4.84-3.313c1.04.563 2.146 1.02 3.3 1.375l1.096 5.852c.103.542.632.982 1.185.982h5.877c.553 0 1.082-.44 1.185-.982l1.086-5.796c1.2-.354 2.354-.82 3.438-1.4l4.902 3.353c.456.313 1.142.25 1.532-.142l4.155-4.154c.39-.39.454-1.076.143-1.532l-3.335-4.874a18.016 18.016 0 0 0 1.424-3.44l5.82-1.09c.54-.104.98-.633.98-1.186v-5.877c0-.553-.44-1.082-.982-1.185zM32 42.085c-5.568 0-10.083-4.515-10.083-10.086 0-5.568 4.515-10.084 10.083-10.084 5.57 0 10.086 4.516 10.086 10.083 0 5.57-4.517 10.085-10.086 10.085z"
fill="blue"
/>
</G>
</Svg>
)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#ecf0f1',
},
});
@msand This is fantastic! Have you tried to use Touchables inside the ZoomableSvg?
Hmm, haven't tried that no. Think this might interfere with that. Should probably detect the time between pressIn and Out, to decide if it should propagate the event to the child or not.
@feluxz Now it should support press handler in the child subtree. Expo: https://snack.expo.io/@msand/zoomablesvg-render-prop-press-handler Vanilla: https://github.com/msand/SVGPodTest/commit/e032be15c4b5c57dae9fd51ce079de2d8dffa917 Changeset: https://github.com/msand/zoomable-svg/commit/9e6f73fee9b12cd11809712edb72d5ab6c156f48
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import { StyleSheet, View, Dimensions } from 'react-native';
import { Svg, G, Circle, Path, Rect } from 'react-native-svg';
import ZoomableSvg from 'zoomable-svg';
const { width, height } = Dimensions.get('window');
export default class App extends Component {
state = {
toggle: false,
};
render() {
const { toggle } = this.state;
return (
<View style={styles.container}>
<ZoomableSvg
align="mid"
width={width}
height={height}
viewBoxSize={65}
svgRoot={({ transform }) => (
<Svg
width={width}
height={height}
viewBox="0 0 65 65"
preserveAspectRatio="xMinYMin meet"
>
<G transform={transform}>
<Rect x="0" y="0" width="65" height="65" fill="white" />
<Circle
cx="32"
cy="32"
r="4.167"
fill={toggle ? 'red' : 'blue'}
onPress={() => this.setState({ toggle: !toggle })}
/>
<Path
d="M55.192 27.87l-5.825-1.092a17.98 17.98 0 0 0-1.392-3.37l3.37-4.928c.312-.456.248-1.142-.143-1.532l-4.155-4.156c-.39-.39-1.076-.454-1.532-.143l-4.928 3.37a18.023 18.023 0 0 0-3.473-1.42l-1.086-5.793c-.103-.543-.632-.983-1.185-.983h-5.877c-.553 0-1.082.44-1.185.983l-1.096 5.85a17.96 17.96 0 0 0-3.334 1.393l-4.866-3.33c-.456-.31-1.142-.247-1.532.144l-4.156 4.156c-.39.39-.454 1.076-.143 1.532l3.35 4.896a18.055 18.055 0 0 0-1.37 3.33L8.807 27.87c-.542.103-.982.632-.982 1.185v5.877c0 .553.44 1.082.982 1.185l5.82 1.09a18.013 18.013 0 0 0 1.4 3.4l-3.31 4.842c-.313.455-.25 1.14.142 1.53l4.155 4.157c.39.39 1.076.454 1.532.143l4.84-3.313c1.04.563 2.146 1.02 3.3 1.375l1.096 5.852c.103.542.632.982 1.185.982h5.877c.553 0 1.082-.44 1.185-.982l1.086-5.796c1.2-.354 2.354-.82 3.438-1.4l4.902 3.353c.456.313 1.142.25 1.532-.142l4.155-4.154c.39-.39.454-1.076.143-1.532l-3.335-4.874a18.016 18.016 0 0 0 1.424-3.44l5.82-1.09c.54-.104.98-.633.98-1.186v-5.877c0-.553-.44-1.082-.982-1.185zM32 42.085c-5.568 0-10.083-4.515-10.083-10.086 0-5.568 4.515-10.084 10.083-10.084 5.57 0 10.086 4.516 10.086 10.083 0 5.57-4.517 10.085-10.086 10.085z"
fill="blue"
/>
</G>
</Svg>
)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#ecf0f1',
},
});
HI @msand -
Just tried out zoomable-svg
, worked like a charm. I wanted to say thanks for your ridiculously fast response time and for going above and beyond anything I expected. Thank you so much!!
I've added support for non-square content, aligning x and y separately and meetOrSlice: https://snack.expo.io/@msand/zoomablesvg-render-prop-press-handler-with-slice
export default class App extends Component {
state = {
toggle: false,
};
render() {
const { toggle } = this.state;
return (
<View style={styles.container}>
<ZoomableSvg
xalign="mid"
yalign="mid"
vbWidth={65}
vbHeight={65}
width={width}
height={height}
meetOrSlice="slice"
svgRoot={({ transform }) => (
<Svg
width={width}
height={height}
viewBox="0 0 65 65"
preserveAspectRatio="xMinYMin meet"
>
<G transform={console.log(transform),transform}>
<Rect x="0" y="0" width="65" height="65" fill="white" />
<Circle
cx="32"
cy="32"
r="4.167"
fill={toggle ? 'red' : 'blue'}
onPress={() => this.setState({ toggle: !toggle })}
/>
<Path
d="M55.192 27.87l-5.825-1.092a17.98 17.98 0 0 0-1.392-3.37l3.37-4.928c.312-.456.248-1.142-.143-1.532l-4.155-4.156c-.39-.39-1.076-.454-1.532-.143l-4.928 3.37a18.023 18.023 0 0 0-3.473-1.42l-1.086-5.793c-.103-.543-.632-.983-1.185-.983h-5.877c-.553 0-1.082.44-1.185.983l-1.096 5.85a17.96 17.96 0 0 0-3.334 1.393l-4.866-3.33c-.456-.31-1.142-.247-1.532.144l-4.156 4.156c-.39.39-.454 1.076-.143 1.532l3.35 4.896a18.055 18.055 0 0 0-1.37 3.33L8.807 27.87c-.542.103-.982.632-.982 1.185v5.877c0 .553.44 1.082.982 1.185l5.82 1.09a18.013 18.013 0 0 0 1.4 3.4l-3.31 4.842c-.313.455-.25 1.14.142 1.53l4.155 4.157c.39.39 1.076.454 1.532.143l4.84-3.313c1.04.563 2.146 1.02 3.3 1.375l1.096 5.852c.103.542.632.982 1.185.982h5.877c.553 0 1.082-.44 1.185-.982l1.086-5.796c1.2-.354 2.354-.82 3.438-1.4l4.902 3.353c.456.313 1.142.25 1.532-.142l4.155-4.154c.39-.39.454-1.076.143-1.532l-3.335-4.874a18.016 18.016 0 0 0 1.424-3.44l5.82-1.09c.54-.104.98-.633.98-1.186v-5.877c0-.553-.44-1.082-.982-1.185zM32 42.085c-5.568 0-10.083-4.515-10.083-10.086 0-5.568 4.515-10.084 10.083-10.084 5.57 0 10.086 4.516 10.086 10.083 0 5.57-4.517 10.085-10.086 10.085z"
fill="blue"
/>
</G>
</Svg>
)}
/>
</View>
);
}
}
Is it possible to add some kind of "safe margin" so that the user can't scroll the view away from their reach when it's zoomed out?
Also, what do you think would be the best way to implement a touch-n-drag override? I want to have a "draw mode" where the view box is locked and any taps/drags the user does are sent to my custom handler (to record coordinate paths) rather than being used to pan/zoom the ZoomableSvg.
It would be possible to set limits on either or both dimensions yes. It would just take min/max of the translations and scaling with the set limits. For draw and move, you either need different states for each, or you need a condition which only allows movement using e.g. pinch, and a large enough threshold on the amount of movement with a single touch before it responds as a draw command. You can perhaps take some inspiration from one of my older projects: http://infinitewhiteboard.com/ https://github.com/Infinify/InfiniteWhiteboard Its more of a laptop/touch screen or mouse adapted thing rather than fit for mobile in any way ;) I released v2 of zoomable-svg, to fully support viewbox transforms: https://snack.expo.io/@msand/zoomablesvg-v2.0.2
@msand I've tried it out on some asymmetric shapes, and i think it's great! It runs perfectly on IOS-device but it stutters on the android-emulator and crashes on device. Any idea why? I also think it would be great with some "safe margins" as @Nexuist says, like in Scrollview with the maximum and minimum-ZoomScale. Your work is much appreciated.
@feluxz Can you try using the plain react-native version (not expo) and the latest version of rnsvg, make a release build and test it on real phone hardware rather than emulation. For me it's smooth as butter on a OnePlus 3. Here's an example project https://github.com/msand/SVGPodTest (uses cocoapods for ios dependency management) I'm not sure why the Expo version keeps crashing on android when releasing after pinch, @brentvatne @dustinsavery could you help here? Is there some special version of rnsvg built into Expo?
@msand Sorry, nothing wrong with the ZoomableSvg! Works great on both IOS and Android, device and emulator. My problem occurs (only on android) when i have a border on top of the ZoomableSvg. Like this:
`
</View>
` Any ideas why this happens?
Hi again @msand -
I’ve now got the ZoomableSvg working quite well in my test app, and now I’m focusing on the drawing aspect of things. My question is, how do I convert locationX and locationY coordinates into coordinates I can use on the actual SVG (with a view box of 720x1000)? With regular web dev I could just use getScreenCTM()
but unfortunately this method is not available in React Native. I’m not sure what I should do to get the CTM from here and actually apply that to the coordinates I get to accomplish my goal of being able to draw an arbitrary path and zoom/pan around it.
@feluxz I'm not sure, could you post a Snack or something? Might be that the coordinates aren't calculated correctly if the root is offset from the top left corner.
@Nexuist You can just invert the transform coming from zoomable-svg, for example like this: https://snack.expo.io/@msand/zoomablesvg-v2.0.2-drawing-with-inverse-transform-example
<Circle
cx="32"
cy="32"
r="4.167"
fill={toggle ? 'red' : 'blue'}
onPress={event => {
const { nativeEvent } = event;
const { locationX, locationY } = nativeEvent;
const {
translateX,
translateY,
scaleX,
scaleY,
} = transform;
const vbX = (locationX - translateX) / scaleX;
const vbY = (locationY - translateY) / scaleY;
this.setState({
toggle: !toggle,
points: [...points, { x: vbX, y: vbY }],
});
}}
/>
{points.map(point => (
<Circle
r="1"
cx={point.x}
cy={point.y}
fill={toggle ? 'blue' : 'red'}
/>
))}
@msand Thanks for reply! I put a header on your project, and got the same result on Android. https://snack.expo.io/HJCW_LjHM
@feluxz Released v2.0.3 and example which seems to work correctly: https://snack.expo.io/ryO6_PsSz
@msand Thank you! It works perfect.
Hi @msand - once again, thank you for your quick reply, it is much appreciated. I have been testing your 2.0.3 example and it almost works perfectly except for one issue. In my <Svg>
component I am using a viewBox of 0 0 720 1000
. This results in all the points being offset from where they should be. Removing the viewBox fixes the problem, but I would like to keep the viewBox because it enables me to center my white rect in the middle of the screen:
class WhiteboardSvg extends ZoomableSvg {
constructor(props) {
super(props);
this.reset = this.reset.bind(this);
this.props.resetCallback(this.reset);
}
reset() {
this.setState({
zoom: 0.6,
left: 131,
top: 131
});
}
}
This results in the rect being centered in the middle of the screen, which is what I want:
Is there a better way to do this without viewBox? If there isn't, what further transformations do I need to apply to vbX and vbY to make them play well with the viewBox?
Thank you so much!
@Nexuist You should remove the viewbox from the svg root, and let zoomable-svg handle it for you, it does the viewbox calculation for you already and has accounted for it in the transform. So just set vbWidth={720} vbHeight={1000} on the ZoomableSvg element instead of the viewbox attribute on the svgRoot.
@msand So i've done that, but here is what I get:
I'd like the rect to be in the center of the screen when the screen first loads.
Here's my WhiteboardSvg:
class WhiteboardSvg extends ZoomableSvg {
constructor(props) {
super(props);
this.reset = this.reset.bind(this);
this.props.resetCallback(this.reset);
}
reset() {
this.setState({
zoom: 0.6
});
}
}
And how I use it in render()
:
<WhiteboardSvg
align="xMidYMid"
meetOrSlice="meet"
vbWidth={720}
vbHeight={1000}
width={this.state.width}
height={this.state.height}
svgRoot={({ transform }) => {
this._CTM = transform;
return (
<Svg
width={this.state.width}
height={this.state.height}
key={this.iterations}
>
<G transform={transform}>
<Rect x="0" y="0" width="720" height="1000" fill="white" />
<Circle cx={this.state.x} cy={this.state.y} r="20" />
<Circle
cx="360"
cy="500"
r="50"
fill={toggle ? "red" : "blue"}
onPress={event => {
const { nativeEvent } = event;
const { locationX, locationY } = nativeEvent;
const {
translateX,
translateY,
scaleX,
scaleY
} = transform;
const vbX = (locationX - translateX) / scaleX;
const vbY = (locationY - translateY) / scaleY;
this.setState({
points: [...this.state.points, { x: vbX, y: vbY }]
});
}}
/>
{this.state.points.map(point => (
<Circle r="1" cx={point.x} cy={point.y} fill="red" />
))}
</G>
</Svg>
);
}}
/>
What can I do to center the rect in the middle of the screen?
Remove your initial zoom, instead set the left (min-x) and top (min-y) values for the viewbox something like this: vbRect={{left: -horizontalMargin, top: -verticalMargin, width: 720 + 2horizontalMargin, height: 1000 + 2 verticalMargin}}
Or, make your content centered on the origin of the coordinate system and have a viewbox vbRect of {left: -halfWidth, top: -halfWidth, width, height} then a zoom of 0.6 would scale everything about the origin, making everything come closer to the middle.
Another way would be to nest another G element with a transform, to translate and/or scale the content, but then you have to account for it in the inverse coordinate transform calculation as well.
@feluxz @Nexuist New version of zoomable-svg published: v2.1.0 now with constraining the extent of zoom and pan. And ability to control zoom and pan by setting zoom, left and/or top prop on the ZoomableSvg element. Here is an example of how to constrain the extent:
<ZoomableSvg
align="mid"
vbWidth={100}
vbHeight={100}
width={width}
height={height}
meetOrSlice="slice"
svgRoot={SvgRoot}
constrain={{
scaleExtent: [0.5, 5],
translateExtent: [[-10, -10], [110, 110]],
}}
/>
It has a similar api to d3.zoom
const {
constrain: {
scaleExtent: [minZoom, maxZoom] = [0, Infinity],
translateExtent: [min, max] = [
[-Infinity, -Infinity],
[Infinity, Infinity],
],
},
} = this.props;
New example, now with passing of child props: https://snack.expo.io/@msand/zoomablesvg-with-childprops,-constrain-and-animation
There were quite a few more ways to handle the combinations of edge cases. I've added a few more strategies for handling the constraints: https://snack.expo.io/@msand/zoomablesvg-v3
const constraintCombinations = [
'none', // Demonstrates no constraints
'dynamic', // Adjusts translate extent according to zoom level and extent (Default and backwards compatible option)
'static', // Statically translate if scale extent allows zooming beyond a translate extent (same behaviour as d3.zoom)
'union', // Take the union of the zoom and translate extent
'intersect', // Take the intersection of zoom and translate extent
];
Seems zoomable-svg covers this now. Closing unless any further issues arise.
zoomable-svg
works fine, but I'm seeing terrible performance with more complex graphics. I'd like to know if anybody else here ran into it. Re-rendering just takes too long.
@adrianboimvaser Do you have any performance test you could contribute? I'm planning to make some optimization changes soon, if time and priorities allow for it. The paths aren't properly reused if only the transforms change now. Should be possible to get quite decent performance improvements with relatively small changes.
@msand I've been using zoomable-svg for a couple months and it mostly works well. There is only one issue where when I zoom, the component randomly jumps around. This is not consistent either so I cannot figure out what is causing it. The component jumps to a new position randomly in the middle of zooming.
@AakashKB Is this on android or ios? I suspect the touch events might be handled by a different target sometimes, not sure why, there seems to be something tricky with gesture responder system.
@msand This is on Android. Thanks for the quick response.
@msand after further testing, the issue occurs on IOS as well but not as often as it occurs on Android.
Hi @msand, I'm trying to understand https://snack.expo.io/@msand/svg-pinch-to-pan-and-zoom and getting lost in the coordinate systems.
We start with the SVG of given height and width (the window in this case). After that, there is a box defined with 0 0 65 65. For me, all shapes under this are in 0-100 range (I'm using %s). Then when someone moves, we call processTouch(x,y) many times with positionX (from position) and positionY (to position).
Then we take the difference between the new positionY and the very first positionX, and add it to the original Left. I lost you here. Are left and top not x and y?
Which coordinate system are touch events (positionX, positionY) happening in? Are they still the same units as the SVG coordinate system? Would changing the box size these things?
I'm trying to figure out the coordinates of the current SVG portion under zoom/pinch/move area. So for example, it could be a box in range (20%, 140%, 80%, -60%) and (20.0001%, 140.002%, 80%, -60%). I'm going to use that to create some more SVG shapes in that area.
Thanks
I think the top-left corner of this area in original coordinates is (newSvgLeft, newSvgTop) = (left * viewBoxSize / width, top * viewBoxSize * height)
, where viewBoxSize would be 65 with this definition. What is the right-bottom corner?
@sanealytics Lets say that top and left are 0 at first., and a touch event occurs, then initialTop/Left are 0, and initialX/Y are the coordinates where the touch started e.g. x = 10 and y = 20. Then for each new event, we take the difference (dx and dy) between the new coordinates and the first touch event, and add this to the initialTop/Left. Lets say you move ten pixels to the right, then x = 20 and y= 20, and initialX = 10 and initialY = 20, thus dx = 10 and dy = 0. The next state is then left = 10 and top = 0.
processTouch(x, y) {
if (!this.state.isMoving || this.state.isZooming) {
const { top, left } = this.state;
this.setState({
isMoving: true,
isZooming: false,
initialLeft: left,
initialTop: top,
initialX: x,
initialY: y,
});
} else {
const { initialX, initialY, initialLeft, initialTop } = this.state;
const dx = x - initialX;
const dy = y - initialY;
this.setState({
left: initialLeft + dx,
top: initialTop + dy,
});
}
}
The logic from that example doesn't implement support for viewBox correctly and doesn't use page relative coordinates for the event handling, which caused some issues on android. For these fixes you can look at https://github.com/msand/zoomable-svg/blob/master/index.js
Here, the scaleX/Y, translateX/Y, input arguments represent the viewBox transform (mapping from the coordinates used in path data and svg components to screen coordinates with optional preserveAspectRatio and meet/slice), and the left, top and zoom are the additional transforms applied because of pan & zoom, and are given in screen relative pixel dimensions.
function getZoomTransform({
left,
top,
zoom,
scaleX,
scaleY,
translateX,
translateY,
}) {
return {
translateX: left + zoom * translateX,
translateY: top + zoom * translateY,
scaleX: zoom * scaleX,
scaleY: zoom * scaleY,
};
}
@sanealytics I've made an example drawing app which might help you grok the transforms and how to add content: https://snack.expo.io/@msand/drawing-with-zoomable-svg
import React, { Component } from 'react';
import {
View,
StyleSheet,
Dimensions,
PanResponder,
TouchableOpacity,
Text,
} from 'react-native';
import { Svg } from 'expo';
import ZoomableSvg from 'zoomable-svg';
const { G, Path, Rect } = Svg;
const { width, height } = Dimensions.get('window');
class SvgRoot extends Component {
state = {
paths: [],
currentPath: null,
};
processTouch = (sx, sy) => {
const { transform } = this.props;
const { currentPath } = this.state;
const { translateX, translateY, scaleX, scaleY } = transform;
const x = (sx - translateX) / scaleX;
const y = (sy - translateY) / scaleY;
if (!currentPath) {
this.setState({ currentPath: `M${x},${y}` });
} else {
this.setState({ currentPath: `${currentPath}L${x},${y}` });
}
};
componentWillMount() {
const noop = () => {};
const yes = () => true;
const shouldRespond = () => {
return this.props.drawing;
};
this._panResponder = PanResponder.create({
onPanResponderGrant: noop,
onPanResponderTerminate: noop,
onShouldBlockNativeResponder: yes,
onMoveShouldSetPanResponder: shouldRespond,
onStartShouldSetPanResponder: shouldRespond,
onPanResponderTerminationRequest: shouldRespond,
onMoveShouldSetPanResponderCapture: shouldRespond,
onStartShouldSetPanResponderCapture: shouldRespond,
onPanResponderMove: ({ nativeEvent: { touches } }) => {
const { length } = touches;
if (length === 1) {
const [{ pageX, pageY }] = touches;
this.processTouch(pageX, pageY);
}
},
onPanResponderRelease: () => {
this.setState(({ paths, currentPath }) => ({
paths: [...paths, currentPath],
currentPath: null,
}));
},
});
}
render() {
const { paths, currentPath } = this.state;
const { transform } = this.props;
return (
<View {...this._panResponder.panHandlers}>
<Svg width={width} height={height} style={styles.absfill}>
<G transform={transform}>
<Rect x="0" y="0" width="100" height="100" fill="white" />
{paths.map(path => (
<Path d={path} stroke="black" strokeWidth="1" fill="none" />
))}
</G>
</Svg>
<Svg width={width} height={height} style={styles.absfill}>
<G transform={transform}>
{currentPath
? <Path
d={currentPath}
stroke="black"
strokeWidth="1"
fill="none"
/>
: null}
</G>
</Svg>
</View>
);
}
}
const constraints = {
combine: 'dynamic',
scaleExtent: [width / height, 5],
translateExtent: [[0, 0], [100, 100]],
};
export default class App extends Component {
state = {
drawing: false,
};
toggleDrawing = () => {
this.setState(({ drawing }) => ({
drawing: !drawing,
}));
};
render() {
const { drawing } = this.state;
return (
<View style={[styles.container, styles.absfill]}>
<ZoomableSvg
align="mid"
vbWidth={100}
vbHeight={100}
width={width}
height={height}
initialTop={0}
initialLeft={0}
initialZoom={1}
doubleTapThreshold={300}
meetOrSlice="meet"
svgRoot={SvgRoot}
lock={drawing}
childProps={this.state}
constrain={constraints}
/>
<TouchableOpacity onPress={this.toggleDrawing} style={styles.button}>
<Text>{drawing ? 'Move' : 'Draw'}</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#ecf0f1',
},
absfill: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
button: {
position: 'absolute',
bottom: 10,
right: 10,
},
});
still digesting this, playing with the code... thanks for your stewardship
I've made a more extended example app if anyone is interested: https://github.com/msand/InfiniDraw/ Universal svg drawing with pan and zoom. Builds on Next.js and react-native-web for the web version, and react-native for native apps. Has a stroke-width slider, a nice color picker modal with fading animation, graphql api and persistence to graph-cool, real-time collaborative updates, almost 100% code sharing across web, ios and android, etc. Can test the current web version here: https://infinidraw-zjiwdgcsln.now.sh/
Is it possible to Pinch to zoom while preserving the quality, as it's vectorized? I've tried ScrollView but it doesn't update the SVG while zoomed in, thus it has bad quality. Just like zooming on raster image.