Pan and Pinch to Zoom with React Use Gesture

  Рет қаралды 20,596

Sam Selikoff

Sam Selikoff

Күн бұрын

Пікірлер: 53
@DavidBismut
@DavidBismut 4 жыл бұрын
Hey Sam, very cool video. I maintain react-use-gesture and wanted to add some additional insights on why you need "touch-action: none" and use non-passive events by setting "eventOptions: { passive: false }". So setting "touch-action: none" is a way to tell mobile browsers that interacting with the element shouldn't trigger any browser scrolling or zooming. Even when you're simply dragging an element (I say this because in the video you're adding touch-action when you move to the pinch gesture), there is a chance that your interaction might be interpreted as a scroll by the browser, so you should almost always want to set "touch-action: none" even when using the drag gesture (note that there are other properties such as 'pan-y', 'pan-x', I won't go into these details here). Now about "eventOptions: { passive: false }": it's a bit more complex. You don't need this on mobile because as seen above "touch-action: none" already takes care of preventing natural browser interaction. But here is what happens on desktop: zooming with your trackpad actually triggers a WheelEvent with the ctrlKey set to 1, and the browser natively interprets this as "the user wants to zoom on the page". Even on non multi-touch trackpads you can emulate the zoom behavior by simply scrolling on your trackpad and pressing the ctrl key. All that react-use-gesture detects is just a WheelEvent. Because the ctrl key is pressed, the lib understands that you want to zoom on the element. The problem is that there's no "wheel-action" property to indicate the browser that it shouldn't zoom on the whole page. So the lib tries to prevent the default behavior on the WheelEvent, to prevent the browser from zooming on the whole page by calling "event.preventDefault()". Now here is where it gets tricky: when you add event listeners for wheel or scroll, Chrome sets them as "passive" by default, for performance reasons (last time I checked, Chrome was the only browser to use passive events). And you can't prevent default on a passive event. So whenever the react-use-gesture detects a pinch gesture triggered by a WheelEvent with the ctrlKey pressed, it flags a warning on your console if it sees that the event isn't cancelable. This is why you need to set passive to false: to let react-use-gesture cancel WheelEvent on Chrome to correctly handle zoom. Addendum: to make things even worse, when you pass the wheel handler to a component the way React wants you to do it (like let's say " e.preventDefault()} />"), you'll get an error on Chrome: in fact, there's no way to tell React to add non-passive listeners, so all React handlers get passive, non cancelable events. And therefore, to support pinch with a trackpad, react-use-gesture asks you to use the "domTarget" option (which is what you're doing in the video) so that it can actually add events to the target via "domTarget.addEventListener('wheel', handleWheel, { passive: false })". Then and only then the events become cancelable and the pinch gesture can support trackpad zooming. Sorry for the long comment but these are things that were hard to figure out for and I thought I would make them clear as I understand they can feel a bit confusing for developers.
@samselikoff
@samselikoff 4 жыл бұрын
Meant to say thank you for this info! And thanks so much for your work on the library :) It sure seems like if there were such a thing as "wheel-action: none" that would be the simplest thing, because then we could use the typical way to attach event handlers (e.g. onWheel). Do you know if it's being worked on in the browser?
@DavidBismut
@DavidBismut 4 жыл бұрын
@@samselikoff sorry to reply this late I’m not used to KZbin notifications. I don’t think this is planned unfortunately or maybe there’s a secret property I don’t know 🤷‍♂️
@unmy400
@unmy400 4 жыл бұрын
nice !! thanks for the video Sam, i'm looking forward for the next ones ! 🚀
@agusterodin
@agusterodin 4 жыл бұрын
Hey, you make awesome videos. Thank you so much! Would love to see pinch to zoom based on where you pinched in future parts.
@samselikoff
@samselikoff 4 жыл бұрын
Coming in the next episode 😉
@uxweb
@uxweb 4 жыл бұрын
Awesome Sam! I am building a UI to display a fullscreen image and let the user to zoom and pan the image, this is going to help me improve the user experience. Thank you!
@agusterodin
@agusterodin 3 жыл бұрын
Were you able to figure out how to get the cropped result?
@helloghoul
@helloghoul 3 жыл бұрын
Great video! Was very useful. Thanks for posting 😁
@swyxTV
@swyxTV 4 жыл бұрын
nice video and love the sweater! the pinching seems centered on the image center - any tricks on how to scale based on the start point of the pinch?
@HkanAktas
@HkanAktas 4 жыл бұрын
Since the browser won't handle transform-offset changes gracefully (it'll be jumpy), one should probably move the x and the y away from pinch point when zooming in, and move them closer to pinch point on zoom out. I bet it gets pretty complicated unlike the simplistic example we have seen in the video.
@samselikoff
@samselikoff 4 жыл бұрын
Great question - will be sure to address this in the series!
@DavidBismut
@DavidBismut 4 жыл бұрын
Here you go - doesn't work with rotate yet as it would probably require advanced calculations, but it does with zoom (on desktop, didn't test on mobile but I'm pretty sure it should work as well) codesandbox.io/s/boring-hertz-sokxs
@DavidBismut
@DavidBismut 4 жыл бұрын
@@HkanAktas That's indeed the logic but it's not THAT complicated codesandbox.io/s/boring-hertz-sokxs
@devstefancho
@devstefancho 2 жыл бұрын
Thank for a great video! How to screen sharing ipad with mac? I want to test touch gesture on the ipad and also simultaneously see chrome console tab on the desktop
@mohammedg485
@mohammedg485 3 жыл бұрын
wow thats impressive ... we need more of that .. keep it up
@tegaogheneovo5881
@tegaogheneovo5881 3 жыл бұрын
0:19 Please bro is this react and how Did you make it fullscreen like that. Thanks
@connerkennedy6691
@connerkennedy6691 Жыл бұрын
Does this work for SVG images as well? I want my SVG to remain interactable and zoomable
@AndroapkzoneBlogspotInapps
@AndroapkzoneBlogspotInapps 2 жыл бұрын
Great video. I implemented as well. But how to disable zoom in and zoom out on mouse scroll?
@thedyslexicwebdev1654
@thedyslexicwebdev1654 Жыл бұрын
Amazing, thank you so much!!
@nazarm6215
@nazarm6215 3 жыл бұрын
Really good explanation. I want to know how cancel works. Guess I gotta dig deeper into the implementation and find out.
@dimitardimitrov4979
@dimitardimitrov4979 4 жыл бұрын
Amazing content! Love it!
@ВладиславПузырев-ю8л
@ВладиславПузырев-ю8л 3 жыл бұрын
I have onPinch triggered if you hold down ctrl and turn the mouse wheel
@nawal7721
@nawal7721 3 жыл бұрын
Hi Sam my image does not seem to drag smoothly like yours. The image only drags by very few pixels and then i have to press click and drag again. Any suggestions? Thanks for the video
@samselikoff
@samselikoff 3 жыл бұрын
It might be because you're on desktop and you'll need to add some extra rules. Try adding touchAction/userSelect rules as seen here: github.com/samselikoff/2020-12-15-image-cropper/blob/9d38f99d1921f22e0c5ed33981542c0ea444c6b1/pages/index.js#L155-L158 There's also some info about it in the docs: use-gesture.netlify.app/docs/faq/#why-cant-i-properly-drag-an-image-or-a-link
@nawal7721
@nawal7721 3 жыл бұрын
@@samselikoff Thank you Sam for your response. To fix the drag I had to use style={{ transform: `translate(${drag.x}px, ${drag.y}px) scale(${zoomScale}) , touchAction: 'none'`}} Anyway thank you for your content. They are unique to the other developers on youtube. I like how you do deep dive to the problems/solutions and communicate that to us. Special channel. Please keep it the content going!
@samselikoff
@samselikoff 3 жыл бұрын
@@nawal7721 You're so welcome and I'm glad you figured it out! Definitely check out the rest of the series as I think you'll like the Framer Motion API. Lets you do scale={zoomScale} x={drag.x}, stuff like that. Simplifies the code a bit and uses requestAnimationFrame to ensure to performance!
@rajdeepchandra9807
@rajdeepchandra9807 2 жыл бұрын
Can we do these on objects on the canvas?
@surjithctly
@surjithctly 4 жыл бұрын
Can you do a video on how can we mimic the iOS Partial Modal (The Swipe up Modal) on Web? Working: Once you click a button, the modal will open Halfway with a grey bar on top indicate you can move up further. Then when we swipe, the modal goes taller and reach top with a curved border.
@무혜-z2j
@무혜-z2j Жыл бұрын
Really cool
@Hangoutapp
@Hangoutapp 3 жыл бұрын
Thanks for the code.
@РусланКононов-ч9я
@РусланКононов-ч9я 4 жыл бұрын
Hi, thanks for your videos on this library, it helps me a lot in my work, please tell me how I can add a delay to the onDrag effect to make it smoother
@samselikoff
@samselikoff 4 жыл бұрын
This would involve adding Framer Motion or React Spring and animating the changing values. I was hoping to be farther along in the series by now but we will get there soon!
@star_gazer_1437
@star_gazer_1437 3 жыл бұрын
Can anyone tell how can we stop the 360 rotation of the image?
@bhatkanti9744
@bhatkanti9744 3 жыл бұрын
can we use it in reactJS?
@agusterodin
@agusterodin 3 жыл бұрын
Does anyone know how to get the cropped result? Trying to do it with canvases.
@agusterodin
@agusterodin 3 жыл бұрын
I've come to the conclusion that computing the result from outside the cropper component is impossible with the given setup. Ideally it would be nice to have enough information in state to be able to compute this from outside the component. I am aware that computing the result inside of the component is trivial since we can get bounding rectangles of both container and image as well as intrinsic image size. In my case I wanted to be able to perform the crop operation in a redux saga task before sending post request to the server. The only way I see get result from outside of component is possible is by having this functional component contain a getResult function that returns minX, minY, maxX, maxY where X and Y refer to dimensions of original image. You could then access this function from outside the component by the means of forwardRef. It makes me wonder if storing state as minX, minY, maxX, maxY (where X and Y refer to dimensions of the original image) would be better than x, y, and scale currently used. The real challenge would be making sure you could derive transforms in a performant way and even harder would be retaining the animations. Storing state in this alternate way could also enable centering of image when initially shown which is something I would really like on the app I'm working on. Thoughts?
@samselikoff
@samselikoff 3 жыл бұрын
@@agusterodin I added an onCropChange callback to the latest version of my image cropper, check out this gist for an example! gist.github.com/samselikoff/4a4e8d5e0ae7dcc1f2eb5149c1362e0c
@agusterodin
@agusterodin 3 жыл бұрын
@@samselikoff God bless you
@agusterodin
@agusterodin 3 жыл бұрын
@@samselikoff I tried pinging you on Twitter too so not sure if you saw but I was planning on adding keyboard navigation (+/- for zoom on current center, arrow keys to move around). Container div would be made focusable and would have a nice little focus-visible ring. Would be a nice a11y addition to this series if you were planning on expanding on it more. Would be trivial compared to the rest of the cropper but I will gladly share my implementation once done.
@jeffhicks6068
@jeffhicks6068 4 жыл бұрын
Very cool
@ThanHtutZaw3
@ThanHtutZaw3 Жыл бұрын
you forget to mention how to get drag snap like intro
@Iammrunkown
@Iammrunkown 3 жыл бұрын
use react-spring instead of react state for animating
@cryptoknight7256
@cryptoknight7256 4 жыл бұрын
I can easily use your code in react native too, correct?
@samselikoff
@samselikoff 4 жыл бұрын
I haven’t tried but I believe so!
@agusterodin
@agusterodin 3 жыл бұрын
This cropper does not work with portrait images
@agusterodin
@agusterodin 3 жыл бұрын
I was able to figure out a good solution: compute the image orientation before showing the page that contains the cropper and do something like this className={`relative ${orientation === 'portrait' ? 'w-full h-auto' : 'w-auto h-full'} max-w-none max-h-none`}
@ruairidhgrass3479
@ruairidhgrass3479 Жыл бұрын
Seems like this only works on mobile :/
@eminqasimovdev
@eminqasimovdev 4 жыл бұрын
i wait framer vs react spring
@samselikoff
@samselikoff 4 жыл бұрын
These days I’m investing Framer Motion since react spring has been in a beta version for over a year
Snapping to Edges with Gesture Callbacks
18:37
Sam Selikoff
Рет қаралды 4,9 М.
Never Forget React forwardRef Again
9:33
Coding in Public
Рет қаралды 25 М.
She made herself an ear of corn from his marmalade candies🌽🌽🌽
00:38
Valja & Maxim Family
Рет қаралды 18 МЛН
Правильный подход к детям
00:18
Beatrise
Рет қаралды 11 МЛН
Что-что Мурсдей говорит? 💭 #симбочка #симба #мурсдей
00:19
Cheerleader Transformation That Left Everyone Speechless! #shorts
00:27
Fabiosa Best Lifehacks
Рет қаралды 16 МЛН
Origin Correction while Pinching to Zoom
17:26
Sam Selikoff
Рет қаралды 3,8 М.
How to build a Recursive React Component
21:16
Sam Selikoff
Рет қаралды 55 М.
Optimizing Rendering Performance in React
7:50
Software Developer Diaries
Рет қаралды 82 М.
Как наука победила религию
17:02
This is the Only Right Way to Write React clean-code - SOLID
18:23
Avoid premature abstraction with Unstyled Components
17:23
Sam Selikoff
Рет қаралды 13 М.
Turning bad React code into senior React code
13:10
Cosden Solutions
Рет қаралды 95 М.
Speed Up Your React Apps With Code Splitting
16:50
Web Dev Simplified
Рет қаралды 402 М.
How to Make a Zoom Parallax using Next.js and Framer Motion
13:13
Olivier Larose
Рет қаралды 32 М.
Learn useReducer In 20 Minutes
20:12
Web Dev Simplified
Рет қаралды 527 М.
She made herself an ear of corn from his marmalade candies🌽🌽🌽
00:38
Valja & Maxim Family
Рет қаралды 18 МЛН