How can I make the native controls on a React Native expo-av Video visible? - reactjs

I need to know how to assign a function to an expo-av Video component's onPress event, or how to make a useRef reference trigger an onPress event on an expo-av Video component.
I have a lot of requirements for a React Native video component and I am trying to work out how to best achieve them all. The main issue is the user needs to be able to double-tap the video and have an animation fire. We have a Pressable component that renders over the video that handles a double tap. The animation works fine. But the problem is there's also a requirement that the video uses native controls, and the Pressable blocks those controls.
I have two strategies but I haven't been able to achieve either. Plan A, someone on StackOverflow tells me that expo-av Video components have some kind of onPress function not listed in their docs, and all I have to do is trigger the double tap animation using that function. So far I've tried onPress, onTouchEnd, onTouchStart, and onClick, and all are undefined and not listed in the docs. No dice.
Plan B, I can create a Pressable component that renders over the Video component that is short enough not to block the native controls, but tall enough to catch all other taps. Then, when a user double-taps the Pressable, the animation fires and everything's good. This is what I initially submitted as a solution, but our QA folks said they need the native controls to show on the first press. Normally the native controls are not visible and the expo-av Video shows them on any press, but the Pressable blocks those presses, and I cannot find anything in the docs that allows you to referentially trigger the expo-av press event. I have a useRef() working and have tried videoRef.current.press, videoRef.current.click, and videoRef.current.setStatusAsync({ progressUpdateIntervalMillis: 0 }). No dice. I just need the control buttons to show up like they normally do on a tap, nothing else.
So in summary, does anyone A: Know how to assign a function to the expo-av Video component's onPress event? or B: Know how to make a useRef reference trigger an onPress event on an expo-av Video component? or C: Some other solution I haven't thought of?
Here are some code snippets if it helps:
The Video instantiation:
<Video
ref={(video) => videoRef.current = video}
style={[styles.postMedia, { width: videoDimensions.width, height: videoDimensions.height }]}
source={{ uri: videoUrl }}
rate={1.0}
volume={1.0}
isMuted={isMuted}
resizeMode={Video.RESIZE_MODE_CONTAIN}
shouldPlay={shouldPlay || screenIsFocussed && (keyInView === _id)}
isLooping
useNativeControls
onError={(e) => setVideoUrl(`${getS3BaseUrl(video.s3Bucket)}/${video.url}`)}
onReadyForDisplay={(response) => {
const { width, height } = response.naturalSize;
if (width >= height) return setVideoDimensions({ width: screenWidth, height: screenWidth * height / width });
if (height > width) return setVideoDimensions({ height: screenWidth, width: screenWidth * width / height });
}}
/>
The handleDoubleTap function:
let lastTap = null;
const handleDoubleTap = () => {
const now = Date.now();
const DOUBLE_PRESS_DELAY = 300;
if (lastTap && (now - lastTap) < DOUBLE_PRESS_DELAY) {
toggleLike();
} else {
if (videoRef) videoRef.current.setStatusAsync({ progressUpdateIntervalMillis: 0 }); // <-- insert something that actually works here
lastTap = now;
}
};
The Pressable component instantiation:
{(video || image) && (
<Pressable
onPress={handleDoubleTap}
style={video ? {
...styles.videoLikePostFromFeedPressable,
height: IS_IOS ? DEVICE_WIDTH * 0.75 : DEVICE_WIDTH * 0.66,
} : {
...styles.likePostFromFeedPressable,
height: DEVICE_WIDTH,
}}
>
{renderOverlay()} {/* <-- This is just the animation render, works fine */}
</Pressable>
)}

Related

Set value immediately after another one have been set in Reanimated 2

I am using Reanimated 2 to build a game with React Native its performance is incredibly good but I have a problem.
I am using a shared value to animate a View as we all know setting the value of the shared value will automatically change the style of the View, my problem is that let's say it will be a Button that the user presses to give the View an elevation simply by changing a shared value used in the animated style of the View, the elevation is simply translation in the y axis.
The elevation value is 0 at first. The user clicks the button the value changes to for example 500 immediately with no transition and no animation, the View will immediately show at 500 above its starting position. And from 500 the View will drop back to 0 with animation.
I tried the code below but no help.
const elevation = useSharedValue(0);
const handleClick = () => {
elevation.value = 500;
elevation.value = withTiming(0, { duration: 1000 });
}
const viewAnimatedStyle = useAnimatedStyle(() => ({
transform: [
{
translateY: elevation.value,
}
]
}))
when pressing the button the view doesn't move, it seems that Reanimated skips the first elevation.value assignment, and since the second assignment is to 0 (the same old value) the View doesn't move.
[Edit] Animated.View is imported from Reanimated 2 and used. I left it out for simplicity.
You need to use <Animated.View> for the useAnimatedStyle, If you are now using It will not be working correctly.
function App() {
const width = useSharedValue(50);
const animatedStyle = useAnimatedStyle(() => {
return {
width: width.value,
};
});
// attach animated style to a View using style property
return <Animated.View style={[styles.box, animatedStyle]} />;
}

How can I make my react-native-camera capture fast?

takePicture = async function() {
if (this.camera) {
const options = { quality: 0.5, base64: true, };
const data = await this.camera.takePictureAsync(options);
this.setState({path: data.uri})
}
}
As soon as I call my takePicture fuction to capture image, the camera still keeps moving and doesn't pause. I want the camera to pause and then show me the image.
Is there something here to with handling Promise? If yes, I don't know where to do it and how.
I've also tried using pauseAfterCapture:true but it still takes 1 or 2 second to capture the image.
I know this is an old issue but no solution could help me yet. Please help.
I've also found the available camera components very slow, that is why I created react-native-fast-camera and I just open sourced it for the public use. It is very customizable and completely controllable by react native components.
Note: Currently the Android version is still work in progress.
Here is an example:
import FastCamera, { Methods } from 'react-native-fast-camera';
<FastCamera style={{ height: cameraHeight, width: cameraWidth }}
onSaveSuccess={imageUrl => {
console.log("onSaveSuccess: ", imageUrl);
}}
onGalleryImage={imageUrl => {
console.log("onGalleryImage: ", imageUrl);
}}
onFlashToggle={isflashOn => {
console.log('flash info: ', isflashOn);
}}
>
{/* here render your buttons to control the camera component */}
<Button
title="capture picture"
onPress={()=> Methods.takePicture();}
/>
</FastCamera>
And here is a screenshot:

How to check to see if a scrollbar is present in React?

I haven't seen a thread that handles this, at least not for React.
My case: I want to conditionally render a back to top button only when scrolling is an option. It makes no sense to have such a feature if it can't affect the page.
The only solutions I can find are in jQuery. I'm using react-scroll but couldn't find any functionality there for this.
When a scrollbar is visible then window.visualViewport.width < window.Width.
var buttonIsVisible = window.visualViewport.width < window.Width;
To check if scrollbar is visible in vertical appearance.
document.body.clientHeight > window.innerHeight
I added this code in a useEffect.
useEffect(() => {
if (document.body.clientHeight > window.innerHeight) {
something()
}
}, [state]);
Luke.
By "scrolling is an option" I am assuming here that you mean "when the scrollbar is visible."
As far as I am aware, there is not any way to check for scrollbar visibility using the React API. There is the DOM boolean window.scrollbars.visible; however, I have not had luck with this. It seems to always return true whether a scrollbar is visible or not. The following approach may work for you:
You could set an event listener for onScroll and check window.scrollY. If window.scrollY > 0, then you could conditionally render the button. If window.scrollY is 0, then the page is already scrolled to the top and there is no need to display the button.
Depending on the design of your web app, checking once for scrollbar visibility (e.g., on componentDidMount) may not be the best option, since some DOM elements may continue to load after the component initially mounts and the height of the page may change.
I hope this is helpful.
If you have a wrapper around the element that has the scroll you can detect the width difference.
<div className="wrapper">
<div className="scrollingContent">
Very long content here
</div>
</div>
const scrollBarWidth = this.wrapper.clientWidth - this.scrollingContent.clientWidth;
this.setState({ scrollBarWidth });
Most of the time (depending on edge cases where elements are sized differently). You can use an element ref to check if the scrollWidth is greater than the current width (or height for vertical scroll). The ref might not update scroll properties with useEffect hence why you need state in the dependencies array. Plus you will likely want to add a window resize event listener to run the same code.
const ref = useRef(null);
const [hasScrollBar, setHasScrollBar] = useState(false);
useEffect(() => {
function updateState() {
const el = ref.current;
el && setHasScrollBar(el.scrollWidth > el.getBoundingClientRect().width);
}
updateState();
window.addEventListener('resize', updateState);
return () => window.removeEventListener('resize', updateState);
}, [state]);
<div ref={ref} style={{ overflowX: 'auto' }}>
{state}
</div>

Animations with React

I have to create some complex animations. It was cool to develop them with jQuery or VanillaJS, but I guess that I should choose another way with React. Google gave me ReactCSSTransitionGroup but it seems to be broken and unmaintained (according to this message: github.com). E.g. I can't make a delay before starting the animation.
I also found a library called React-motion but I'm not sure if it's actually a tool that I need. So I want to ask whether you can recommend me something about it. Probably I should use VanillaJS (using refs and other ReactDOM functions)? Or there is another approach to it? Thanks in advance.
The easiest way to do animations in React, or, in fact, anywhere on the web, is to use CSS Transitions.
CSS Transitions actually has nothing to do with React. Quoting MDN,
CSS Transitions is a module of CSS that lets you create
gradual transitions between the values of specific CSS properties. The
behavior of these transitions can be controlled by specifying their
timing function, duration, and other attributes.
Because CSS transitions is a pure CSS, they can be used in React applications, Angular, plain Javascript or even old-school server-rendered pages with no Javascript at all.
It is not the most versatile or powerful technique. But since in most cases the animations we want are pretty simple, why looking for something more complicated when a simple will do the job?
CSS Transitions are also well-supported by all major browsers with a notable exception of Opera Mini and IE below version 10.
CSS Transitions give us an ability to animate between the two CSS states. Let's say you want to animate opening and closing of a drawer (triggered by a click on a button). Let's assume we have a flex container around the drawer. When the drawer is opened, we want it to occupy 100% of the container width, therefore its max-width should be 100%. When it is closed, its width should be 0. We can create two CSS styles:
/* This CSS style is applied when the drawer is opened */
const openedStyle = {
maxWidth: '100%' /* max-with is 100% when the drawer is opened */,
};
/* This CSS style is applied when the drawer is closed */
const closedStyle = {
maxWidth: 0 /* max-width is 0 in the closed drawer */,
};
Then we handle opening / closing event apply one of those classes to the drawer object on opening / closing:
class Drawer extends React.Component {
state = {
opened: false // Initially search form is Closed
};
toggleOpened = () =>
// Toggle opened / closed state.
// Because we rely on the previous state, we need to use
// a functional setState form
// https://ozmoroz.com/2018/11/why-my-setstate-doesnt-work/
this.setState(state => ({ ...state, opened: !state.opened }));
render() {
const { opened } = this.state;
return (
<div className="drawer-container col-12 col-md-4">
<input
type="text"
className="drawer"
// Apply 'openedStyle' CSS class if the drawer is opened,
// and 'closedStyle' if the drawer is closed.
style={opened ? openedStyle : closedStyle}
/>
<button
type="button"
className="open-close-button btn btn-primary"
onClick={this.toggleOpened}
>
{opened ? 'Close' : 'Open'}
</button>
</div>
);
}
}
export default Drawer;
When we press the "Open / Close" button, the drawer will flip between 0 and 100% width, effectively opening and closing.
All we need now is to animate it.
For that we need a secret ingredient - a CSS transition property. All we need to do is to add the following style to the drawer:
/* This CSS style is applied when the drawer is opened */
const openedStyle = {
maxWidth: '100%' /* max-with is 100% when the drawer is opened */,
/* Upon transitioning to Open,
animate `max-width' for 0.5s*/
transition: 'max-width 0.5s'
};
/* This CSS style is applied when the drawer is closed */
const closedStyle = {
maxWidth: 0 /* max-width is 0 in the closed drawer */,
/* Upon transitioning to Closed,
animate `max-width' for 0.5s */
transition: 'max-width 0.5s'
};
VoilĂ ! We've got our animation - upon clicking the button our drawer is now expanded and collapsed within half a second!
This is it in the nutshell, but there is more to it:
You can animate more than one CSS attribute with a CSS transition.
You can apply delay to transitionable properties, i.e. animate opacity first, and then animate width of the same element after 0.5 second delay.
You can apply various timing functions to transitions.
I wrote an expanded blog post explaining all the above: Painless React Animations via CSS Transitions.
Check out this easy to use and popular package:
https://www.npmjs.com/package/react-transition-group
Install:
npm install react-transition-group
Usage:
import { CSSTransition } from 'react-transition-group';
<CSSTransition
in={toShow} // boolean value passed via state/props to either mount or unmount this component
timeout={300}
classNames='my-element' // IMP!
unmountOnExit
>
<ComponentToBeAnimated />
</CSSTransition>
NOTE: Make sure to apply below styles using the class property in CSS:
.my-element-enter {
opacity: 0;
transform: scale(0.9);
}
.my-element-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 300ms;
}
.my-element-exit {
opacity: 1;
}
.my-element-exit-active {
opacity: 0;
transform: scale(0.9);
transition: opacity 300ms, transform 300ms;
}
Maybe react-magic can help you.

Is it possible to combine 'px' and 'vh' into the height of 1 component in react js

I have a few components and some of them are sized with px. The others I want to be variable size. However, the ones that are variable size are not a constant percentage of the page because of the components with a fixed px height. So I want a component to be about 80% of the screen height('80vh') minus the height of the other component. I was hoping to use something like
style={{height:'80vh-40px'}}
but that does not work.
I found this page which gets close but that my program does not recognize that calc function. Do I need to require it from some library maybe?
Any ideas on how to make this work?
Thanks!
I use syntax like this in my React.js components:
<div style={{height: 'calc(100vh - 400px)'}}></div>
it works for me.
Inside CSS code with LESS preprocessor I use the same syntax, but not equal:
.right_col {
height: calc(~"100vh - 400px");
}
In my experiments, I found that symbol "~" doesn't work in React.js components.
A way to solve it is by using style={} in your component.
const styles = {targetDiv: { height: 'calc(100vh - Xpx)'}}
then...
<div style={styles.targetDiv}>
...
</div>
Note - you can get the window.innerHeight (see http://ryanve.com/lab/dimensions/ for height and width options in javascript) and just calculate this yourself in react and set the width or height in pixels.
If you do this with height, you'd need to recalculate if it changes
state = {
windowHeight: window.innerHeight
}
componentWillMount () {
window.addEventListener('resize', this.handleResize)
}
componentWillUnmount () {
window.removeEventListener('resize', this.handleResize)
}
handleResize = () => {
this.setState({ windowHeight: window.innerHeight })
}
in your div
<div style={{height: this.state.windowHeight}} > ... </div>
You wouldn't want to do this everywhere, but it's very handy for some situations. For example - setting 100% height on Android mobile web, where 100vh does not work and device buttons cover up part of the screen.

Resources