Angular Youtube Player - Fit to 100% height - css

I use the Youtube Angular pakacge in my Angular 11 project. I would like to fill the player to 100% of the divs height, which is a TailWind h-full div:
<div class="flex flex-col flex-auto w-full h-full xs:p-2" #videoContainer>
<youtube-player
*ngIf="videoId"
[videoId]="videoId"
width="100%"
[height]="videoHeight"
></youtube-player>
</div>
I tried to do this in two different eays already:
#1 height="100%" or height="100vh"
Both leads to:
#2 Dynamic Height
[height]="videoHeight"
ngOnInit() {
this._params = this._route.params.subscribe((params) => {
this.videoId = params['videoId'];
});
}
ngAfterViewInit(): void {
this.videoHeight = this.videoContainer.nativeElement.offsetHeight;
}
This works, but leads to
Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: '905'..
#3 Move videoHeight
I moved this.videoHeight = this.videoContainer.nativeElement.offsetHeight; to the constructor and in the OnInit this leads to:
TypeError: Cannot read property 'nativeElement' of undefined at new YoutubeComponent
What am I doing wrong?

Here is how I solved it,
I created new component:
ng g component components/yt-player
In yt-player.component.html add container with ref and youtube-player
<div #youTubePlayer >
<youtube-player [width]="videoWidth" [height]="videoHeight" [videoId]="videoID"></youtube-player>
</div>
and in yt-player.component.ts add this code:
export class YtPlayerComponent implements AfterViewInit {
#ViewChild('youTubePlayer') youTubePlayer: ElementRef<HTMLDivElement>;
videoHeight: number | undefined;
videoWidth: number | undefined;
#Input('videoID') videoID: string;
constructor(private changeDetectorRef: ChangeDetectorRef) {}
ngAfterViewInit(): void {
this.onResize();
window.addEventListener('resize', this.onResize.bind(this));
}
onResize(): void {
// you can remove this line if you want to have wider video player than 1200px
this.videoWidth = Math.min(
this.youTubePlayer.nativeElement.clientWidth,
1200
);
// so you keep the ratio
this.videoHeight = this.videoWidth * 0.6;
this.changeDetectorRef.detectChanges();
}
}
The code is basiclly self explenatory, you have refrence on the container of youtube-player, in afterViewInit you set videoHeight and videoWidth, to corespond to the width of the container. We set up event listener in the case of size changes to update the width and height. And in the end we add #Input('videoID') videoID: string so we can pass the id to the youtube-player component. So we can use it like this:
<yt-player videoID="oHg5SJYRHA0"></yt-player>

Try height="100vh"
Try assigning value to videoHeight during declaration or in constructor
or in ngOnInit hook.

Remove any attempt to set width and height in the template code -
<div class="flex flex-col flex-auto w-full h-full xs:p-2">
<youtube-player *ngIf="videoId" [videoId]="videoId"></youtube-player>
</div>
and also from the component code. Then it should display automatically to full height.

Related

Importing React State into CSS file

I am currently working on a NextJS based project, in which I need to use React State to determine the width of a div. Currently this is being calculated inside a .tsx file using consts and incremented when a button is clicked. The resultant width is then passed down as a prop to the component.
Right now, I'm using inline styling to set the width of the div, but I wanted to know if it was possible to pass the prop directly into the .module.css file I'm using as it will be tidier and much easier for media queries later on. Is that possible?
Also, is it possible to import variables from the media queries back into the .tsx file?
Main file:
const [width, setWidth] = React.useState(0)
const increment: number = maxWidthTop / totalRounds
export default function Labeller() {
function clicked() {
setWidth(width + increment)
}
return (
<Progress
width={width} />
)}
Component file:
import styles from '../../styles/Progress.module.css'
type ProgressProps = {
width: number;
}
export default function ProgressBar(props: ProgressProps) {
return (
<div className={styles.main}>
<div className={styles.bar}>
<div className={styles.base}></div>
<div style={{width: `${props.width}em`}} className={styles.top}></div>
</div>
</div>
)
}
You can't modify the css module dynamically at runtime as the css files are downloaded and parsed by the browser separately from the js. Supplying your width value using inline is styles a good way to go but you are right that it doesn't make media queries easy.
One alternative option would be to write a function that formats a css string with your width variable, and renders the output inside a style element:
import "./styles.css";
const divWidthStyles = (width: number) => `
.${styles.top} {
width: ${width}px;
}
#media only screen and (min-width: 600px) {
.${styles.top} {
width: ${2 * width}px;
}
}
`;
...
export default function ProgressBar(props: ProgressProps) {
return (
<div className={styles.main}>
/* Use style element */
<style>{divWidthStyles(props.width)}</style>
<div className={styles.bar}>
<div className={styles.base}></div>
<div className={styles.top}></div>
</div>
</div>
);
}
Another option would be to set a css variable using javascript, whenever width changes, by making use of useEffect(). Like so:
function ProgressBar(props: ProgressProps) {
const { width } = props;
const divRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (divRef.current) {
divRef.current.style.setProperty("--width", `${width}px`);
}
}, [width]);
return (
...
<div
className={"dynamic_width"}
ref={(ref) => (divRef.current = ref)}
></div>
...
);
}
And then making use of the variable in your css.module file like this:
.dynamic_width {
height: 200px;
background: red;
width: var(--width);
}
#media only screen and (min-width: 600px) {
.dynamic_width {
width: calc(2 * var(--width));
}
}
Side note; there's a library called styled-components that allows you to supply values to the styles of components easily. I find it helps to keep things very tidy and could be a more elegant solution to your problem.

How to absolutely position the last item separately in Tailwind?

I want to just set the left value of a absolutely positioned div different from other divs. I tried the last: property of tailwind, but its not working.
Here's my code thet I tried
absolute z-10 -ml-4 transform px-2 w-screen max-w-md sm:px-0 lg:ml-0 -left-5 last:left-20"
I added these classes and only want the last mapped div to have different position
Sample code:
<div class='relative w-full h-72'>
{items.map((each, i ) => {
return (
<div key={i} class='absolute top-0 left-0 last:left-10'>{each}</div
)
})}
</div>
and the last div that is mapped should have different left value
Prior to v2.1 and JIT mode, you need to explicitly enable the last-child variant in your tailwind.config.js, as it's not enabled for any core plugins.
// tailwind.config.js
module.exports = {
// ...
variants: {
extend: {
inset: ['last']
}
}
}
Playground demo: https://play.tailwindcss.com/Wt6reZBsRY
From v2.1, when using Just-in-Time mode no extra configuration is required as all styles are generated on-demand.
// tailwind.config.js
module.exports = {
mode: 'jit',
// ...
}

Next.js Image- how to maintain aspect ratio and add letterboxes when needed

I have an area of my Next.js app which is the selected photo of a photo gallery so it has to stay fixed in size as people flip through the selected image or a photo is loading. I have a responsive layout but if really pressed, I'd say that this pixel area is 566px*425px.
I'm confused about how to actually do this. Here's the closest I've been able to get it, but the problem is that I get overflow of the image when the aspect ratio exceeds 566x425 and for images that have an aspect ratio below 566x425 it will stretch it in the Y direct. What I really want is to have a fixed box and then if the aspect ratios differ from the max size, you'll see letterboxes either along the sides or on the top and bottom.
<div
style={{
position: 'relative',
width: '566px',
height: '425px',
}}
>
<Image
src={currCommit.image.url}
alt="Current Image"
layout={'fill'}
objectFit="cover"
/>
</div>
Ooh I got it! The key was to set the parent div to a fixed size and relative and then set the Image to a layout of fill and an objectFit of contain. The only downside to this approach is I need to set media queries so it will scale for smaller sizes.
<div className="relative item-detail">
<Image src={currCommit.image.url} alt="Current Image" layout={'fill'} objectFit={'contain'} />
</div>
Then in the css I set:
.item-detail {
width: 300px;
height: 225px;
}
there's better solution i think, NextImage have callback property onLoadingComplete:
A callback function that is invoked once the image is completely loaded and the placeholder has been removed.
The onLoadingComplete function accepts one parameter, an object with the following properties: naturalWidth, naturalHeight
you can use the natural properties to set image ratio without loosing NextImage's layout functionality like this:
const NaturalImage = (props: ImageProps) => {
const [ratio, setRatio] = useState(16/9) // default to 16:9
return (
<NextImage
{...props}
// set the dimension (affected by layout)
width={200}
height={200 / ratio}
layout="fixed" // you can use "responsive", "fill" or the default "intrinsic"
onLoadingComplete={({ naturalWidth, naturalHeight }) =>
setRatio(naturalWidth / naturalHeight)
}
/>
)
}
the only downside is the aspect ratio applied only after image loaded, so the placeholder using the default ratio (in this case 16:9 - common), and this can cause CLS
Next13 onwards, layout and objectFit have been deprecated in favour of intrinsic style properties. This actually makes our job easier as you can now style the image like regular CSS, like so:
import Image from "next/image";
<div style={{ position: 'relative', width: '566px', height: '425px'}}>
<Image fill
src={currCommit.image.url}
alt="Current Image"
style={{objectFit: 'cover'}}
/>
</div>
According to bayu's asnwer,
You can create a custom component called RatioNextImage and use it like.
<RatioNextImage src={put_your_URL_here} alt={put_the_alt_here}/>
In RatioNextImage.tsx
import NextImage from "next/image";
import { useState } from "react";
interface Props {
src: string;
alt: string;
}
const RatioNextImage = ({ src, alt }: Props) => {
const [ratio, setRatio] = useState(16 / 9); // this value can be anything by default, could be 1 if you want a square
return (
<NextImage
src={src}
width={200}
height={200 / ratio}
layout="fixed"
onLoadingComplete={({ naturalWidth, naturalHeight }) => {
setRatio(naturalWidth / naturalHeight);
}}
/>
);
};
export default RatioNextImage;
The best solution that I could find that does not require to specify exact width or height.
<Image
layout="responsive"
width="100%"
height="62.5%" // 16:10 aspect ratio
objectFit="cover"
src={src}
/>

In React,How to make a page that contains two dynamic size components

If data in container A collapses(minimised), Component B should increase vertically in size and appear on full page. Similarly if Component B is collapsed,component A should increase.By default,both the components have equal screen space.
there are tons of ways to do this, you can check how flexbox in CSS works. it should not bee very react specific, All you need to do from react is to know which component is collapsed and which is to expanded.
In the parent component, you'll want to track which component is maximised. Then, pass a maximised prop to component A and component B, and let them set their CSS classes based on it. You could hide most of the content if you just want a mini version of the component.
Assuming you're using function components with hooks, it would look somewhat like this:
const Container = () => {
// Either "A", "B" or null (equal sizes)
const [componentMaximised, setComponentMaximised] = useState(null);
return (
<div className="container">
<A maximised={componentMaximised === "A"}/>
<B maximised={componentMaximised === "B"}/>
</div>
);
};
const A = props => {
return (
<div className={props.maximised ? "component component-maximised" : "component"}>
// ...
</div>
);
};
const B = props => {
return (
<div className={props.maximised ? "component component-maximised" : "component"}>
// ...
</div>
);
};
You'll also want to pass the setComponentMaximised function to each component as a prop if you want them to be able to have a button to maximise and minimise themselves.
For your CSS, use display: flex in combination with flex-grow to set how the items share the space:
.container {
height: 100vh; /* approx full height */
display: flex;
flex-flow: column nowrap;
}
.component {
flex-grow: 1;
overflow: hidden; /* prevent contents from spilling out of component */
}
.component-maximised {
flex-grow: 3;
}
Quick demo of this technique (try changing the classes manually in HTML)
https://codepen.io/gh102003/pen/MWKOQqE
You can use flex-grow: 0 if you just want the component to take up the space it needs.

Is there a way to make a css property reactive with vuejs #scroll?

I'm making a vuejs component in my project, and i need to create a zoom with scroll, in a div (like googlemaps).
<div #scroll="scroll">
<Plotly :data="info" :layout="layout" :display-mode-bar="false"></Plotly>
</div>
<style>
div {
transform: scale(property1);
}
<\style>
<script>
export default {
methods: {
scroll(event) {
},
},
created() {
window.addEventListener('scroll', this.scroll);
},
destroyed() {
window.removeEventListener('scroll', this.scroll);
}
}
</script>
How can i make a method that make the "property1" reactive? Or there is another way to zoom with scroll only the div?
you can bind a dynamic style object to your div which includes the transform property with a dynamic value (see docs for deeper explanation):
<div #scroll="scroll" :style="{ transform : `scale(${property1})`}">
<Plotly :data="info" :layout="layout" :display-mode-bar="false"></Plotly>
</div>
new Vue({
...
data() {
return {
property1: defaultValue
}
},
methods : {
scroll(e){
// e is the event emitted from the scroll listener
this.property1 = someValue
}
}
})
You can also add some modifiers in the template as shown in the documentation to reduce the method code (here we could be preventing the page from scrolling whenever the user will be scrolling while hovering this specific element):
<div #scroll.self.stop="scroll" :style="{ transform : `scale(${property1})`}">
<Plotly :data="info" :layout="layout" :display-mode-bar="false"></Plotly>
</div>

Resources