I am making a kind of personal website with a full screen video in background. And I need to load a different video for different screen sizes. I am using React, and making a component based on React Hooks.
These are my dependencies version:
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.1.2",
"react-scripts": "3.4.1",
Inside my component "VideoBackground", I created a state with useState where I will store the source of the video and the source of the poster image, like this:
const [video, setVideo] = useState({
poster: null,
src: null,
});
And I am using useEffect hook to work as the componentDidMount function, where I am going to load the video dynamically based on the windows width and add a function to the event listeners in the Browser for reload the video every time that a resizing happens:
useEffect(() => {
async function handleResize() {
setVideo(await loadVideo());
}
handleResize();
window.addEventListener("resize", handleResize);
window.addEventListener("orientationchange", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
window.removeEventListener("orientationchange", handleResize);
};
}, []);
The function "loadVideo" that load the video dynamically based on the windows width is a async function because I am using the Dinamic Import syntax provided by React which is a Promise (That is the biggest problem I guess). This is my full component, the "loadVideo" function is at the beginning:
The "./styles" file is a component based on styled-component lib, I am using it to style my html, it doesn't matter (I guess). But the "Video" component is the video tag from html 5.
import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { Video, Overlay, Content } from "./styles";
const VideoBackground = ({ children }) => {
/*
* Media query of the resource's intended media; this should be used only in a <picture> element
*
* Extra large devices (large laptops and desktops, 1200px and up)
* Large devices (laptops/desktops, 992px and up)
* Medium devices (landscape tablets, 768px and up)
* Small devices (portrait tablets and large phones, 600px and up)
* Extra small devices (phones, 600px and down)
*/
async function loadVideo() {
const { width } = getWindowDimensions();
let posterPromise;
let srcPromise;
if (width > 1280) {
posterPromise = import("../../assets/images/code720.jpg");
srcPromise = import("../../assets/videos/code720.mp4");
} else if (width > 720) {
posterPromise = import("../../assets/images/code480.jpg");
srcPromise = import("../../assets/videos/code480.mp4");
} else if (width > 480) {
posterPromise = import("../../assets/images/code320.jpg");
srcPromise = import("../../assets/videos/code320.mp4");
} else {
posterPromise = import("../../assets/images/code240.jpg");
srcPromise = import("../../assets/videos/code240.mp4");
}
const [{ default: poster }, { default: src }] = await Promise.all([
posterPromise,
srcPromise,
]);
return {
poster,
src,
};
}
/*
* It returns the width of a window's content area
*/
function getWindowDimensions() {
const { innerWidth: width } = window;
return {
width,
};
}
const [video, setVideo] = useState({
poster: null,
src: null,
});
useEffect(() => {
async function handleResize() {
setVideo(await loadVideo());
}
handleResize();
window.addEventListener("resize", handleResize);
window.addEventListener("orientationchange", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
window.removeEventListener("orientationchange", handleResize);
};
}, []);
console.log("BEFORE RETURN", video);
return (
<>
<Video playsInline autoPlay muted loop poster={video.poster}>
<source src={video.src} type="video/mp4" />
</Video>
<Overlay />
<Content>{children}</Content>
</>
);
};
VideoBackground.propTypes = {
children: PropTypes.node.isRequired,
};
export default VideoBackground;
The problem is that the background video is not playing. But the poster image appears, and I dont know why just the poster image is working. I put that console.log before the return to try to find out what is happening, and this is what is logged:
BEFORE RETURN {poster: null, src: null}
BEFORE RETURN {poster: null, src: null}
BEFORE RETURN {poster: "/static/media/code720.e66bf519.jpg", src: "/static/media/code720.83f62f35.mp4"}
BEFORE RETURN {poster: "/static/media/code720.e66bf519.jpg", src: "/static/media/code720.83f62f35.mp4"}
As you see, the video and the images are loaded normally (But after some time). I think that the problem is that the "useState" assigns a null value to the properties of my state for the first time, and just after, the "useEffect" calls the "loadVideo" function.
I tried to import and assign a video to the "useState" for the first time, and normally works! However, I cannot have a video started with the "useState", because I need to know the screen size before and download the appropriate video.
I also tried to use the "loadVideo" function with the "useState", like this:
const [video, setVideo] = useState(loadVideo());
But, it doesnt work because "loadVideo" is async.
Does anyone have any solution to this problem, or a better way for what I want to do?
Thank you!