how to make react webcam responsive - reactjs

[`
import React, { useCallback, useRef, useState } from 'react';
import Webcam from 'react-webcam';
import RadioButtonUncheckedIcon from '#material-ui/icons/RadioButtonUnchecked';
import { useDispatch } from 'react-redux';
import { setCameraImage } from './features/cameraSlice';
import { useHistory } from 'react-router-dom';
import './WebcamCapture.css';
var elem =
document.compatMode === 'CSS1Compat'
? document.documentElement
: document.body;
const videoConstraints = {
width: elem.clientWidth,
height: elem.clientHeight,
facingMode: 'environment',
};
function WebcamCapture() {
const webcamRef = useRef(null);
const dispatch = useDispatch();
const history = useHistory();
const capture = useCallback(() => {
const imageSrc = webcamRef.current.getScreenshot();
dispatch(setCameraImage(imageSrc));
history.push('/preview');
}, [webcamRef]);
return (
<div className="webcamCapture">
<Webcam
audio={false}
height={videoConstraints.height}
ref={webcamRef}
screenshotFormat="image/jpeg"
width={videoConstraints.width}
videoConstraints={videoConstraints}
/>
<RadioButtonUncheckedIcon
className="webcamCapture__button"
onClick={capture}
fontSize="large"
/>
</div>
);
}
export default WebcamCapture;
.webcamCapture {
position: relative;
width: 100%;
}
.webcamCapture__button {
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
color: white;
}
`]1
`I want the video element to take after the screen's dimensions and take up 100% of the screen view at all times, including when browser is resized or when done on mobile but there seems to be a certain ratio restriction.
for mobile devices its not taking up the entire screen
please help me with the entire code so that it's responsive on all devices

To apply a custom style for mobile screens, you will have to detect mobile screens first using
#media then apply custom style.
I have added a solution below which I expect to work, detecting 640px & 768px max width devices and I have added height:100%;, width:100%;.
.webcamCapture {
position: relative;
width: 100%;
}
#media only screen and (max-device-width : 640px) {
.webcamCapture {
position:fixed;
height:100%;
width:100%;
}
}
#media only screen and (max-device-width: 768px) {
.webcamCapture {
position:fixed;
height:100%;
width:100%;
}
}

Related

Animation Problem with Framer Motion and LayoutGroup

strong textI'm trying to make a Pokedex App with an animation when we click on a card.
I have a Component PokemonList who render PokemonCard.
When i click on PokemonCard, I navigate to /{pokemonName} and an detail component shows up with an animation.
The Detail component is rendered with of React router
My PokemonCard and my PokemonView (Detail pannel) are in the same Framer Motion LayoutGroup. My cards have the layoutId card-${pokemonInfo.name} and my PokemonView have too. On the opening, i fetch current pokemon with the location.pathname and fill detail pannel with infos.
PokemonView.ts
import { LayoutGroup, motion } from "framer-motion"
import { useLocation, useNavigate } from "react-router-dom"
import styled from "styled-components"
import { colours } from "../../data/typeColors"
import { hexToRgb } from "../../helpers/hexToRgb"
import { useDisableScroll } from "../../hooks/useDisableScroll"
import {
useGetPokemonInfoQuery,
useGetPokemonSpeciesInfoQuery,
} from "../../redux/api/api"
import PokemonInfoPannel from "./PokemonInfoPannel"
import ViewTab from "./Tabs/ViewTab"
const PokemonViewContainer = styled.div`
height: 100%;
width: 100vw;
background-color: ${(props: { background: string }) =>
hexToRgb(props.background, 0.5)};
position: fixed;
top: 0;
display: flex;
justify-content: center;
align-items: center;
`
const PokemonViewStyle = styled(motion.div)`
background-color: ${(props: { background: string }) => props.background};
height: 100%;
width: 100%;
min-height: 680px;
max-width: 600px;
max-height: 800px;
overflow: hidden;
opacity: 1;
#media (min-width: 600px) {
border-radius: 2rem;
box-shadow: 0 0 10px 0 rgba(255, 255, 255, 0.5);
}
#media (max-width: 600px) {
max-height: 100vh;
}
`
const PokemonView = () => {
console.log("PokemonView")
const navigate = useNavigate()
useDisableScroll()
const location = useLocation()
const { data: pokemonInfo } = useGetPokemonInfoQuery(
location.pathname.split("/")[1]
)
const { data: pokemonSpeciesInfo } = useGetPokemonSpeciesInfoQuery(
location.pathname.split("/")[1]
)
return (
<>
<PokemonViewContainer
id="pokemon-view-container"
background={colours[pokemonInfo!.type[0]].background}
onClick={(e) => {
navigate("/")
}}
>
<LayoutGroup id="card-swap-animation">
<PokemonViewStyle
layoutId={`card-${pokemonInfo.name}`}
id="pokemon-view"
background={colours[pokemonInfo!.type[0]].background}
onClick={(e) => {
e.stopPropagation()
}}
>
{/* <PokemonInfoPannel
pokemonDetailed={{
info: pokemonInfo!,
speciesInfo: pokemonSpeciesInfo!,
}}
></PokemonInfoPannel>
<ViewTab
pokemonDetailed={{
info: pokemonInfo!,
speciesInfo: pokemonSpeciesInfo!,
}}
></ViewTab> */}
</PokemonViewStyle>
</LayoutGroup>
</PokemonViewContainer>
</>
)
}
export default PokemonView
With this I have the correct behavior. But, I have some typescript errors because my pokemonInfo and pokemonSpeciesInfo can be undefined (i fill them via RTK-Query).
So, I decided to add this before the return
if (!pokemonInfo || !pokemonSpeciesInfo) {
return null
}
But with this line, the first time i click on a card the animation doesn't work. I close detail pannel, open it again and the animation works.
Another think that I don't understand is that when I do that
const { data: pokemonInfo } = useGetPokemonInfoQuery(
location.pathname.split("/")[1]
)
console.log(pokemonInfo)
const { data: pokemonSpeciesInfo } = useGetPokemonSpeciesInfoQuery(
location.pathname.split("/")[1]
)
console.log(pokemonSpeciesInfo)
I see that the first request is never undefined but the second is always, then became fullfilled. I don't know if this is the problem.
Someone can help me ?
Thanks in advance
The source code is here : https://github.com/RomainHoffmann/Pokedex
UPDATE :
I understood why the first console was never null. It's because I do this request on my PokemonCard component, so the response is cached by RTK Query.
To fix my problem, I add the other query in my Card component, it works but I call it "in the void".
I have understand now thats returning <></> in the first render makes the problem but how could I do to avoid Typescript errors and render the component only when datas are fullfiled ?

Passing a direction prop into styled-components keyframe component?

I'm trying to implement a reusable text animation component where a direction prop can be passed in. I probably close but doesn't seem to be working. Also open to better ways of implementing it a better way.
import React from "react"
import styled from "styled-components"
import { GlobalStyles } from "./global"
import TextAnimations from "./Components/TextAnimations"
const Container = styled.div`
display: flex;
justify-content: space-between;
flex-wrap: wrap;
`
const NavBar = styled.nav`
background: #3a3a55;
padding: 0.25rem;
width: 100%;
height: 10vh;
`
const Main = styled.main`
background: #3a3a55;
color: white;
padding: 0.25rem;
flex: 10 1 auto;
height: 100vh;
`
const Break = styled.div`
flex-basis: 100%;
width: 0;
`
function App() {
return (
<>
<GlobalStyles />
<Container>
<NavBar>NavBar</NavBar>
<Break />
<Main>
<TextAnimations text='Sample text from the left' direction='left' />
<TextAnimations text='Sample text from the right' direction='right' />
</Main>
</Container>
</>
)
}
export default App
and then the animation component:
import { motion } from "framer-motion"
import styled, { keyframes } from "styled-components"
type TextAnimationProps = {
text: string
direction: string
}
const Left = keyframes`
0% { left: -3.125em; }
100% { left: 3em;}
`
const Right = keyframes`
0% { Right: -3.125em; }
100% { Right: 3em;}
`
const HomeHeader = styled.div`
h1 {
font-weight: lighter;
}
position: relative;
top: 0;
animation: ${(props) => (props.defaultValue === "left" ? Left : Right)} // is this right?
animation-duration: 3s;
animation-fill-mode: forwards;
`
const TextAnimations = ({ text, direction }: TextAnimationProps) => {
return (
<HomeHeader defaultValue={direction}>
<h1>{text}</h1>
</HomeHeader>
)
}
export default TextAnimations
I'd also like to make it more flexible so that I can add 'top', 'bottom', etc.
Is this the best way to handle text animations?
You can create a separate function to set the animation. The function will receive the props of the styled component from which the function will access the direction prop.
const setHomeHeaderAnimation = ({ direction = "left" }) => {
const animation = keyframes`
0% {
${direction}: -3.125em;
}
100% {
${direction}: 3em;
}
`;
return css`
position: relative;
animation: ${animation};
animation-duration: 3s;
animation-fill-mode: forwards;
`;
};
const HomeHeader = styled.div`
${setHomeHeaderAnimation}
h1 {
font-weight: lighter;
}
`;
const App = () => (
<div>
<HomeHeader>
<div>Some text</div>
</HomeHeader>
<HomeHeader direction="right">
<div>Some text</div>
</HomeHeader>
<HomeHeader direction="top">
<div>Some text</div>
</HomeHeader>
<HomeHeader direction="bottom">
<div>Some text</div>
</HomeHeader>
</div>
);

Use Navigate function not working (React)

I'm following a tutorial and everything was going great until I tried to implement Navigation through a search input. For instance, If I am on http://localhost:3000/searched/profile. Typing out an input of 'names' in the search bar should take me to http://localhost:3000/searched/names. In the tutorial it worked that way and I believe I did the same thing but it doesn't work for me
First below is the Search component for the search bar and its input
And then the Pages where my routing is done. My Browser Router is in the App.js.
import styled from "styled-components"
import { FaSearch } from 'react-icons/fa'
import { useState } from 'react'
import {useNavigate} from 'react-router-dom'
function Search() {
const [input, setInput] = useState('');
const navigate = useNavigate();
const submitHandler = (e) => {
e.preventDefault();
navigate('/searched/' + input) (I GUESS THIS IS WHAT IS NOT WORKING)
};
return (
<FormStyle onSubmit={submitHandler}>
<div>
<FaSearch></FaSearch>
<input onChange={(e) => setInput(e.target.value)} type="text" value={input}/>
</div>
<h1>{input}</h1>
</FormStyle>
)
}
const FormStyle = styled.div`
margin: 0 20rem;
div{
width: 100%;
position: relative;
}
input{
border: none;
background: linear-gradient(35deg, #494949, #313131);
border-radius: 1rem;
outline: none;
font-size: 1.5rem;
padding: 1rem 3rem;
color: white;
width: 100%;
}
svg{
position: absolute;
top: 50%;
left: 0%;
transform: translate(100%, -50%);
color: white;
}
`
export default Search
Pages
import Home from "./Home"
import { Route, Routes } from 'react-router-dom'
import Cuisine from "./Cuisine"
import Searched from "./Searched"
function Pages() {
return (
<Routes>
<Route path='/' element={<Home/>} />
<Route path='/cuisine/:type' element={<Cuisine/>} />
<Route path='/searched/:search' element={<Searched/>} />
</Routes>
)
}
export default Pages
The FormStyle component is a styled div element instead of a form element, so the onSubmit handler is meaningless on the div. To resolve you should use the form element so the form submission works as you are expecting.
Search.js Example:
import styled from "styled-components";
import { FaSearch } from "react-icons/fa";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
function Search() {
const [input, setInput] = useState("");
const navigate = useNavigate();
const submitHandler = (e) => {
e.preventDefault();
navigate("/searched/" + input);
};
return (
<FormStyle onSubmit={submitHandler}> // <-- (2) onSubmit works now
<div>
<FaSearch></FaSearch>
<input
onChange={(e) => setInput(e.target.value)}
type="text"
value={input}
/>
</div>
<h1>{input}</h1>
</FormStyle>
);
}
const FormStyle = styled.form` // <-- (1) switch to form element
margin: 0 20rem;
div {
width: 100%;
position: relative;
}
input {
border: none;
background: linear-gradient(35deg, #494949, #313131);
border-radius: 1rem;
outline: none;
font-size: 1.5rem;
padding: 1rem 3rem;
color: white;
width: 100%;
}
svg {
position: absolute;
top: 50%;
left: 0%;
transform: translate(100%, -50%);
color: white;
}
`;
export default Search;

Custom arrow Swiper Slider + Next.js + Sass

I'm using the slider swiper in a project developed in Next.JS and I'm using Sass to do the styling. But when I'm going to use the swiper classes, as mandated by the documentation, to style the arrows, it doesn't work.
I need the arrows to be outside the component, not overlapping.
CSS
.swiper-button-next,
.swiper-button-prev {
background: red;
position: absolute;
top: 50%;
width: calc(var(--swiper-navigation-size) / 44 * 27);
height: var(--swiper-navigation-size);
margin-top: calc(0px - (var(--swiper-navigation-size) / 2));
z-index: 10;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--swiper-navigation-color, var(--swiper-theme-color));
}
.swiper-button-next.swiper-button-disabled,
.swiper-button-prev.swiper-button-disabled {
opacity: 0.35;
cursor: auto;
pointer-events: none;
}
.swiper-button-next:after,
.swiper-button-prev:after {
background: red;
font-family: swiper-icons;
font-size: var(--swiper-navigation-size);
text-transform: none !important;
letter-spacing: 0;
text-transform: none;
font-variant: initial;
line-height: 1;
}
.swiper-button-prev,
.swiper-container-rtl .swiper-button-next {
left: 10px;
right: auto;
}
.swiper-button-prev:after,
.swiper-container-rtl .swiper-button-next:after {
content: "prev";
color: #000;
}
.swiper-button-next,
.swiper-container-rtl .swiper-button-prev {
right: 10px;
left: auto;
}
.swiper-button-next:after,
.swiper-container-rtl .swiper-button-prev:after {
content: "next";
}
.swiper-button-next.swiper-button-white,
.swiper-button-prev.swiper-button-white {
--swiper-navigation-color: #ffffff;
}
.swiper-button-next.swiper-button-black,
.swiper-button-prev.swiper-button-black {
--swiper-navigation-color: #000000;
}
.swiper-button-lock {
display: none;
}
I've tried changing the styles, but nothing reflects on the component. If I change styles by browser it works normally.
Changing the basic arrows styles is pretty simple - take a look at the following codesandbox: https://codesandbox.io/s/stoic-shaw-q35wq?file=/src/styles.scss
.swiper-button-prev, .swiper-button-next {
top: 45%;
width: 40px;
height: 40px;
background: #fff;
border: 1px solid gray;
border-radius: 50%;
color: blue;
font-weight: 700;
outline: 0;
transition: background-color .2s ease, color .2s ease;
&::after {
font-size: 16px;
}
}
.swiper-button-prev {
&:after {
position: relative;
left: -1px;
}
}
.swiper-button-next {
&:after {
position: relative;
left: 1px;
}
}
.swiper-button-prev, .swiper-container-rtl .swiper-button-next {
left: 10px;
right: auto;
}
.swiper-button-next, .swiper-container-rtl .swiper-button-prev {
right: 10px;
left: auto;
}
.swiper-button-prev.swiper-button-disabled, .swiper-button-next.swiper-button-disabled {
opacity: 0;
cursor: auto;
pointer-events: none;
}
Moving the arrows outside, however, is a bit more tricky. In the example above I've used a little CSS tricks (negative margin and corresponding padding) and some overflows to get it working but it might not be enough for your use case.
You would have to create your own next/previous elements:
import React from "react";
import { Navigation, Pagination, Scrollbar, A11y, Controller } from "swiper";
import { Swiper, SwiperSlide } from "swiper/react";
// Import Swiper styles
import "swiper/css";
import "swiper/css/navigation";
import "swiper/css/pagination";
import "swiper/css/scrollbar";
import "./styles.scss";
const image = "https://source.unsplash.com/featured/300x201";
export default function App() {
const images = new Array(6).fill({ url: image });
const [swiper, setSwiper] = React.useState();
const prevRef = React.useRef();
const nextRef = React.useRef();
React.useEffect(() => {
if (swiper) {
console.log("Swiper instance:", swiper);
swiper.params.navigation.prevEl = prevRef.current;
swiper.params.navigation.nextEl = nextRef.current;
swiper.navigation.init();
swiper.navigation.update();
}
}, [swiper]);
return (
<div className="App">
<div className="carousel-container">
<div className="swiper-button" ref={prevRef}>
prev
</div>
<Swiper
modules={[Navigation, Pagination, Scrollbar, A11y, Controller]}
className="external-buttons"
spaceBetween={24}
slidesPerView={1}
navigation={{
prevEl: prevRef?.current,
nextEl: nextRef?.current
}}
updateOnWindowResize
observer
observeParents
initialSlide={2}
onSwiper={setSwiper}
>
{images.map((image, index) => (
<SwiperSlide key={index}>
<img
height="200"
width="300"
alt="img"
className="image"
src={image.url}
/>
</SwiperSlide>
))}
</Swiper>
<div className="swiper-button" ref={nextRef}>
next
</div>
</div>
</div>
);
}
Complete example - https://codesandbox.io/s/prod-darkness-o483y?file=/src/App.js
Important - the examples are using Swiper v7
Here is the best way to add custom arrows:
import React, { useRef } from "react";
// For Typescript
// import SwiperCore from "swiper";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
const SliderComponent = () => {
const swiperRef = useRef();
// For Typescript!
// const swiperRef = useRef<SwiperCore>();
const sliderSettings = {
440: {
slidesPerView: 1,
spaceBetween: 30,
},
680: {
slidesPerView: 2,
spaceBetween: 30,
},
1024: {
slidesPerView: 3,
spaceBetween: 30,
},
};
return (
<div>
<button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>
<Swiper
slidesPerView={3}
breakpoints={sliderSettings}
onBeforeInit={(swiper) => {
swiperRef.current = swiper;
}}
>
<SwiperSlide>
Slide 1
</SwiperSlide>
<SwiperSlide>
Slide 2
</SwiperSlide>
<SwiperSlide>
Slide 3
</SwiperSlide>
<SwiperSlide>
Slide 4
</SwiperSlide>
<SwiperSlide>
Slide 5
</SwiperSlide>
</Swiper>
<button onClick={() => swiperRef.current?.slideNext()}>Next</button>
</div>
);
};
export default SliderComponent;
hamza liaqat's answer is correct, but for typescript you need to add
import { Swiper as SwiperCore } from 'swiper/types';

How to loop a Draggable slider in React

I have a draggable horizontal slider in my current project and I would like to setting up it also to loop continuously. By loop continuously I mean it should respond to the process of showing images one after another when dragging? Right now, I do have only 3 images in my slider and when I drag slider to the left, slider with its 3rd image and a blank white space starts showing just after. Here at this point I want images to get start again continuously from the very beginning i.e. from the 1st image with aim to cover the white blank space.
Apart, one error I'm getting with my existing code is that when I start to drag slider to right side, suddenly a scroll comes up on browser and keep going in never ending state. By never ending state, I mean it still remain on screen when I drag all my 3 images fully in right direction.
So these are the two things I want to apply and want to resolve in my current project. I'm sharing my code below.
src > Routes > Home > Components > Carousel > Components > SliderDataItems > index.js
import React, { useRef, useEffect } from "react";
import { gsap } from "gsap";
import { Draggable } from "gsap/Draggable";
import { ZoomInOutlined } from '#ant-design/icons'
import { Images } from '../../../../../../Shared/Assets';
import ImagesIcon from '../../../../../../Components/Cells/ImagesIcon'
gsap.registerPlugin(Draggable);
const pictures = [
{
img: Images.xgallery1,
icon: <ZoomInOutlined />
},
{
img: Images.xgallery2,
icon: <ZoomInOutlined />
},
{
img: Images.xgallery4,
icon: <ZoomInOutlined />
},
];
const Slide = ({ img, icon }) => {
return (
<div className="slide">
<div className="image">
<ImagesIcon src={img} />
<div className="icon">
{icon}
</div>
</div>
</div>
);
};
export const Slider = () => {
const sliderRef = useRef(null);
useEffect(() => {
Draggable.create(sliderRef.current, {
type: "x"
});
}, []);
return (
<div className="slider" ref={sliderRef}>
{pictures.map((item, index) => {
return (
<Slide key={index} img={item.img} icon={item.icon} />
);
})}
</div>
);
};
export default Slider;
src > Routes > Home > Components > Carousel > style.scss
.slider {
display: flex;
cursor: unset !important;
overflow: hidden !important;
.slide {
.image {
position: relative;
img {
width: 100% !important;
height: auto !important;
object-fit: cover;
}
.icon {
transition: 0.5s ease;
opacity: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
text-align: center;
span {
svg {
font-size: 30px;
color: #fff;
}
}
}
}
}
.image:hover .icon {
opacity: 1;
}
}
.image:after {
content: "";
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(211, 208, 208, 0.6);
opacity: 0;
transition: all 0.5s;
-webkit-transition: all 0.5s;
}
.image:hover:after {
opacity: 1;
}
Here's the link of demo (kindly see just above the footer section) for your reference.
Thank you for any help.
For draggle Slider there is a very lightweight JS Carousel package - siema
It is a great, lightweight carousel that is made with JS. There are also other packages built on top of this purely made for React.
In your case, I would offer to try out react-siema.
With it, you can simply use the carousel like that and it will be draggable by default. Plus, no need to load any css.

Resources