Next image dynamic size and aspect ratio - css

I'm creating a blog page using next.js with one main image for each post. The image is of unknown dimensions and an unknown aspect ratio (as each post has a different image). How can I make the image maintain its own aspect ratio and just scale on smaller screens without specifying a fixed height?
I'm forced to use fill with a fixed height container, but that just distorts the image due to the fixed height. The first image below is next/image with fixed height, and the one below it is a plain img tag. How can I get next image to behave like the img tag with dynamic images?
I want each image to maintain its initial aspect ratio, but not lock all images to the same aspect ratio. Is it even possible as it will break next.js rule on layout shift?
Code for next image:
<div className="container relative imageContainer rounded-lg mb-4 overflow-hidden max-w-7xl">
<Image
src={props.post.imageUrl}
alt={props.post.title}
layout="fill"
/>
</div>
imageContainer CSS:
.imageContainer {
height: 30rem;
}
Code for img tag:
<img
className="rounded-lg mb-4 "
src={props.post.imageUrl}
alt={props.post.title}
></img>

One of the main goals of next/image is performance-related, which as you mentioned, involves the elimination of CLS entirely by always requiring some dimensions.
You have a couple of options here.
Option 1 - Use local images
If you are storing all of your blog images with the source code in some sort of /assets folder, you can import them and use them like so and they will not require width or height and will assume their original dimensions (source docs)
import localImage from './assets/some-local-image.png'
function ExampleComponent() {
return (
<Image
src={localImage}
alt="local image"
/>
)
}
Option 2 - Use getStaticProps + probe-image-size
I'm assuming you're reusing a single, generic <Post /> component or something similar, so if you wanted to dynamically grab widths and heights from a list, you could retrieve that info server-side in getStaticProps and pass as props to the component. This comes with a slight performance hit on your server, but assuming you're not regenerating these images often, it shouldn't be a huge problem.
import probe from 'probe-image-size'
function ExampleComponent(props) {
return (
<Image
src={props.img.src}
width={props.img.width}
height={props.img.height}
alt={props.img.alt}
/>
)
}
export async function getStaticProps() {
// Probably will come from a route param, etc.
const imgPath = '/public/some-path.png'
const img = fs.createReadStream(path.join(process.cwd(), imgPath))
// Read img dimensions
const probedImg = await probe(img)
return {
props: {
img: {
width: probedImg.width,
height: probedImg.height,
src: imgPath,
alt: 'some dynamic alt attribute'
}
}
}
}
Option 3 - use an image CDN
Most image CDNs will give you the ability to transform images on the fly and will provide all of these properties for you. You can set them up with a custom Next.js loader.
Option 4 - use the regular <img /> HTML tag
If none of the above solutions work, it's likely that image optimization isn't your #1 concern, and therefore, using the native image tag works totally fine and shouldn't cause any conflicts with other Next.js <Image /> elements on your page.

Related

Vuetify navbar (v-app-bar) overlap scrollbar

The same problem as this post except it is Vuetify.
Is there any solution that use provided API? CSS would be the last option I choose.
codepen demo
<template>
<v-app-bar app dark absolute class="navbar-bg" >
<v-app-bar-nav-icon #click="toggleSidebar" />
<v-toolbar-title>Homepage</v-toolbar-title>
</v-app-bar>
</template>
Currently, there's no a single prop in API.
But you may help yourself a lot with a built-in vuetify classes and directives.
First of all, you (sadly) need to write some CSS to manually disable initial page scrolling:
html {
overflow-y: hidden;
}
.scrollable {
overflow-y: scroll;
}
Then you need to add <v-main> component to your application with scrollable pt-0 mt-16 classes and wrap all of your future app components into it. This classes will adjust the padding from the default <v-app-bar> and enable scrolling directly in <v-main>.
Finally, you should add v-resize directive to <v-main> to automatically recalculate your page size when user will resize a page:
<v-main class="scrollable pt-0 mt-16" v-resize="onResize">
...your application data...
</v-main>
...
methods: {
onResize() {
//64px is v-app-bar height in your case
document.querySelector(".scrollable").style.height = (window.innerHeight - 64) + 'px';
}
},
That's it. You may then create your custom component to wrap <v-main> and forget about such manipulations.
Codepen link with an example

Is there a way to set up "loading: lazy" instead of default "loading: eager" for all inline images in gatsby-source-wordpress?

I'm trying to decrease page loading time. Right now all images which came from Wordpress content are geting "loading: eager". In result all images are downloading immediately all together on the page. So I would like to know is there an option to set up by default "loading: lazy" for all images which come from content of gatsby-source-wordpress.
To show images I'm just using this way:
<div dangerouslySetInnerHTML={{ __html: content }} />
Gatsby v3.14,
NodeJs 12.22.6,
Gatsby-source-wordpress 5.14,
Gatsby-plugin-image 1.14
I think your best chance is customizing the content that is rendering the dangerouslySetInnerHTML or trying gatsby-wpgraphql-inline-images
For the first approach, a library such as html-react-parser may fit your requirements.
Instead of:
<div dangerouslySetInnerHtml={{__html: content}}/>
You will do:
<div>{parse(content, {replace: replaceMedia})}</div>
Where replaceMedia is a function that gets the nodes and replaces them by a custom markup:
const replaceMedia = node => {
if (node.name === 'img') {
console.log("Check the node data:", node)
return <img src={node.attribs.src} loading="lazy" alt="Some alternative text" />
}
};
Test it to check the data inside node and tweak it accordingly. If your images are locally set, you can even use a custom component to return a GatsbyImage.
The second approach will rely on your set up, which has not been provided in the question.
Useful resources:
https://dimitr.im/optimize-loading-images-wordpress-gatsby
https://www.gatsbyjs.com/plugins/gatsby-wpgraphql-inline-images/
https://www.gatsbyjs.com/plugins/gatsby-wpgraphql-inline-images/

Sign In With Google button responsive design

Is there any way to make the new "Sign In With Google" button responsive? Specifically, vary the width based on the width of the containing element? I'd really just like to set the width to 100%.
I'm aware I can set the data-width attribute but this sets it to an explicit width and doesn't update if you change it after the initial script load - you have to reload the whole script to resize the width.
This isn't a perfect solution but it works for us. We're using Twitter Bootstrap.
The new JavaScript library has a renderButton method. You can therefore render the button multiple times on one page passing different widths to each button using something like this (400 is the max width allowed by the library)
private renderAllGoogleSignInButtons(): void {
this.renderGoogleSignInButton(document.getElementById('google-signin-xs'), 400);
this.renderGoogleSignInButton(document.getElementById('google-signin-sm'), 280);
this.renderGoogleSignInButton(document.getElementById('google-signin-md'), 372);
this.renderGoogleSignInButton(document.getElementById('google-signin-lg'), 400);
this.renderGoogleSignInButton(document.getElementById('google-signin-xl'), 400);
}
private renderGoogleSignInButton(element: HTMLElement, width: number){
const options {
type: 'standard',
....
width: width
};
google.accounts.id.renderButton(element, options);
}
We then use the display classes from bootstrap to hide/show each button depending on the size.
<div class="mx-auto" style="max-width: 400px">
<div class="d-none-sm d-none-md d-none-lg d-none-xl">
<div id="google-signin-xs"></div>
</div>
<div class="d-none d-none-md d-none-lg d-none-xl">
<div id="google-signin-sm"></div>
</div>
<div class="d-none d-none-sm d-none-lg d-none-xl">
<div id="google-signin-md"></div>
</div>
<div class="d-none d-none-sm d-none-md d-none-xl">
<div id="google-signin-lg"></div>
</div>
<div class="d-none d-none-sm d-none-md d-none-lg">
<div id="google-signin-xl"></div>
</div>
</div>
We use a wrapper container with mx-auto and a max-width to center the buttons but you don't have to do this.
Our actual implementation is slightly different than the above as we're using Angular and the button is a component but you can get the idea from the above.
The only drawback with this method is that the "personalized button" doesn't seem to display for all rendered buttons but it doesn't seem to affect their functionality.
This answer is based on the new Google Identity Services.
You could try listening for a resize in the window using the resize event, then re-render the Google Sign In button on change. The assumption here is that the container will respond to match the window size:
addEventListener('resize', (event) => {});
onresize = (event) => {
const element = document.getElementById('someContainer');
if (element) {
renderGoogleButton(document.getElementById('googleButton'), element.offsetWidth); // adjust to whatever proportion of the "container" you like
}
}
renderGoogleButton(element, width) {
const options = {
type: 'outline',
width: width
}
google.accounts.id.renderButton(element, options);
}
I've also had better results when the button is centered, not left aligned. The following in Bootstrap:
<div class="d-flex justify-content-center">
<div id="googleButton"></div>
</div>
NB: The max width for the Google button as of the time of writing is 400px, so bear that value in mind as the limit.
I did a workaround, and it worked for me. As I needed the button to have 100% width in mobile devices.
If you have another element on the screen that behaves the same way you need (like having its width 100%), you can select it using a querySelector, and get its width element.clientWidth, after this you can pass the width to the renderButton function provided by google.
But this solution is not valid if you would like the button to change its size on resizing.
I used transform: scale like this in the CSS:
.sign_in_btn_wrapper {
transform: scale(1.5, 1.5);
float: left;
margin-left: 20vmin;
font-weight: bold;
}
Then, instead of wrapping it as I intended, I found that it was fine to just add the class directly to the goog div:
<div class="g_id_signin sign_in_btn_wrapper"
data-type="standard"
data-shape="rectangular"
data-theme="outline"
data-text="signin_with"
data-size="large"
data-logo_alignment="left"
data-width="250">
</div>
By fiddling with combinations of data-size and data-width, along with the scaling factors, I was able to make it the size I wanted. You can use CSS media queries to adjust the 'transform: scale' values so that it is 'Responsive' to the display size of the user's device. You could also use other trickier methods by having JS tweak variables in your CSS that are then used to set the scaling factors.
Good luck. You'd think it'd be in the interest of these big 'sign in with' providers to get together a coordinating working group to make it easier for web site developers to make all the sign-in buttons the same damn size -- you know they'd rather not have their button come out smaller, and pages look better when things are uniform. And what's with only having dimensions in pixels? At least give us vw, vh, and my favorite: vmin. (Using vmin to set things like font size means you can often skip more tedious RWD contortions and call it good enough.) </end_rant>

Tailwind: Split layout with a video component?

I'm trying to build this split layout with Tailwind and React:
But after countless attempts, I only managed to achieve this layout. Using the exact same styling with an image instead of a video works, but I'm unaible to scale / stretch the video container.
I'm under the assumption that this might be related to the width and height properties of the ReactPlayer component, but every attempt to set these at different percentages in order to improve the layout failed.
<div className="bg-green w-full md:w-1/2 order-first lg:order-last md:order-last">
<ReactPlayer
url= {TennisVideo}
className="object-contain h-screen md:h-screen lg:h-screen xl:h-screen w-full "
width='100%'
height='100%'
playing={true}
muted={true}
loop={true}
config={{ youtube: { playerVars: { disablekb: 1 } } }}>
</ReactPlayer>
</div>
Can anyone point me in the right direction?
https://codesandbox.io/s/sharp-hugle-vpuvr?file=/src/App.js
I would imagine that object-contain is a culprit, and should instead be object-cover. More info in the docs here

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}
/>

Resources