Skip to content

Instantly share code, notes, and snippets.

@monecchi
Created February 26, 2021 14:57
Show Gist options
  • Save monecchi/f6d73655f47e07be1cc00b469cfa93b6 to your computer and use it in GitHub Desktop.
Save monecchi/f6d73655f47e07be1cc00b469cfa93b6 to your computer and use it in GitHub Desktop.

Revisions

  1. @TheThirdRace TheThirdRace revised this gist Dec 5, 2020. 1 changed file with 14 additions and 3 deletions.
    17 changes: 14 additions & 3 deletions image.ts
    Original file line number Diff line number Diff line change
    @@ -4,8 +4,19 @@
    * Keep the values in sync between:
    * - `deviceSizes` in `next.config.js`
    * - `deviceSizes` in `image.ts`
    * - `contentMaxWidthInPixel` in `page.ts`
    * - value needs to be included in `deviceSizes`
    *
    * ! Recommended
    * NextJs optimize images according to your viewport. This is wonderful for mobile, but for desktop with a 4k screen, NextJs would
    * download the 3840px version of your image.
    *
    * To workaround this unfortunate situation, I highly recommend you pass `size` to images with the highest width value being the
    * max width an image can have on your site.
    *
    * For example, content on my site is centered and cannot be more than 960px wide, thus I make sure that 960 is in `deviceSizes` and
    * I use `Sizes.main` to limit the image to only 960px. This considerably reduce the size in KB of my images and they're much
    * better optimized on screens larger than 960px.
    *
    * This file is a way to generate the strings to pass to Image's `size` property and put the results in an Enum for easier consumption
    */
    const deviceSizes = [320, 480, 640, 750, 828, 960, 1080, 1200, 1440, 1920, 2048, 2560, 3840]
    const deviceSizesMax = Math.max(...deviceSizes)
    @@ -24,7 +35,7 @@ const generateSizes = (upperLimit: number = deviceSizesMax): string => {
    })
    .join()
    }
    // console.log(generateSizes(contentMaxWidthInPixel))
    // console.log(generateSizes(960)) // I use a variable, but since it's easier to understand with a real number...
    // console.log(generateSizes())

    export enum Sizes {
  2. @TheThirdRace TheThirdRace revised this gist Dec 5, 2020. 2 changed files with 49 additions and 12 deletions.
    28 changes: 16 additions & 12 deletions Image.tsx
    Original file line number Diff line number Diff line change
    @@ -1,11 +1,13 @@
    import { chakra, ThemingProps, useStyleConfig } from '@chakra-ui/react'
    import NextImage, { ImageProps as NextImageProps } from 'next/image'
    import { ReactElement } from 'react'
    import { Sizes } from '../../theme/variables/image'

    // TODO review props when NextJs is updated so we don't have to defined it here
    /**
    * ? Because NextJs typing is preventing auto-suggest for layout, width and height
    * ? Because NextJs typing is preventing auto-suggest for layout, width and height,
    * ? we declare the styles differently in this component and will manage the switch
    * ? to NextJs typings when calling NextJs Image component
    * ? to NextJs typings when calling NextJs `next/image` component
    */
    type LayoutValue = 'fixed' | 'intrinsic' | 'responsive' | undefined

    @@ -20,8 +22,7 @@ type LayoutAndSize =
    }

    /**
    * Types for the Image component
    * Picking only the props I want to allow, you can adjust to your liking
    * Types for the Image component itself
    */
    type ImageProps = Pick<
    NextImageProps,
    @@ -31,10 +32,11 @@ type ImageProps = Pick<
    Pick<ThemingProps, 'variant'> & {
    dimensions?: [number, number]
    layout?: 'fill' | LayoutValue
    sizes?: Sizes // could be a string too, this one is just a way to make it easier
    }

    /**
    * Wrap NextJs Image component in Chakra's factory function
    * Wraps NextJs `next/image` component in Chakra's factory function
    * This is what will allow to use the theme and the styling properties on the component
    */
    const ImageWithChakra = chakra(
    @@ -47,13 +49,14 @@ const ImageWithChakra = chakra(
    objectPosition,
    priority,
    quality,
    sizes,
    src,
    unoptimized,
    ...nextjsInternals
    }: ImageProps): ReactElement => {
    /**
    * As explained earlier, NextJs typing is preventing auto-suggest for layout, width and height
    * Here we actual convert our component typing to NextJs typing
    * ? As explained earlier, NextJs typing is preventing auto-suggest for layout, width and height
    * ? Here we actually convert our component typing to NextJs typing
    */
    const [width, height] = dimensions

    @@ -68,9 +71,6 @@ const ImageWithChakra = chakra(
    layout: 'fill'
    }

    /**
    * The actual NextImage component
    */
    return (
    <NextImage
    className={className}
    @@ -79,9 +79,12 @@ const ImageWithChakra = chakra(
    objectPosition={objectPosition}
    priority={priority}
    quality={quality}
    sizes={sizes}
    src={src}
    unoptimized={unoptimized}
    // eslint-disable-next-line react/jsx-props-no-spreading
    {...layoutAndSize}
    // eslint-disable-next-line react/jsx-props-no-spreading
    {...nextjsInternals}
    />
    )
    @@ -90,9 +93,10 @@ const ImageWithChakra = chakra(

    export const Image = ({ variant, ...props }: ImageProps): ReactElement => {
    /**
    * This components serves as an interface to pass Chakra's styles
    * You can use the theme and/or styling properties (eg. backgroundColor='red.200')
    * ? This components serves as an interface to pass Chakra's styles
    * ? You can use the theme and/or styling properties (eg. backgroundColor='red.200')
    */
    const styles = useStyleConfig('Image', { variant })
    // eslint-disable-next-line react/jsx-props-no-spreading
    return <ImageWithChakra sx={styles} {...props} />
    }
    33 changes: 33 additions & 0 deletions image.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,33 @@
    /**
    * ! Important in optimizing images
    *
    * Keep the values in sync between:
    * - `deviceSizes` in `next.config.js`
    * - `deviceSizes` in `image.ts`
    * - `contentMaxWidthInPixel` in `page.ts`
    * - value needs to be included in `deviceSizes`
    */
    const deviceSizes = [320, 480, 640, 750, 828, 960, 1080, 1200, 1440, 1920, 2048, 2560, 3840]
    const deviceSizesMax = Math.max(...deviceSizes)

    /**
    * ? `generateSizes` will create the strings necessary for `Sizes` enum
    *
    * ? Simply uncomment the `console.log` and adjust values
    */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const generateSizes = (upperLimit: number = deviceSizesMax): string => {
    const sizes = [...deviceSizes.filter((v) => v < upperLimit), upperLimit]
    return sizes
    .map((v, i) => {
    return i < sizes.length - 1 ? ` (max-width: ${v}px) ${v}px` : ` ${v}px`
    })
    .join()
    }
    // console.log(generateSizes(contentMaxWidthInPixel))
    // console.log(generateSizes())

    export enum Sizes {
    main = '(max-width: 320px) 320px, (max-width: 480px) 480px, (max-width: 640px) 640px, (max-width: 750px) 750px, (max-width: 828px) 828px, 960px',
    full = '(max-width: 320px) 320px, (max-width: 480px) 480px, (max-width: 640px) 640px, (max-width: 750px) 750px, (max-width: 828px) 828px, (max-width: 960px) 960px, (max-width: 1080px) 1080px, (max-width: 1200px) 1200px, (max-width: 1440px) 1440px, (max-width: 1920px) 1920px, (max-width: 2048px) 2048px, (max-width: 2560px) 2560px, 3840px'
    }
  3. @TheThirdRace TheThirdRace revised this gist Nov 28, 2020. No changes.
  4. @TheThirdRace TheThirdRace revised this gist Nov 28, 2020. No changes.
  5. @TheThirdRace TheThirdRace revised this gist Nov 28, 2020. No changes.
  6. @TheThirdRace TheThirdRace revised this gist Nov 28, 2020. No changes.
  7. @TheThirdRace TheThirdRace created this gist Nov 28, 2020.
    98 changes: 98 additions & 0 deletions Image.tsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,98 @@
    import { chakra, ThemingProps, useStyleConfig } from '@chakra-ui/react'
    import NextImage, { ImageProps as NextImageProps } from 'next/image'
    import { ReactElement } from 'react'

    /**
    * ? Because NextJs typing is preventing auto-suggest for layout, width and height
    * ? we declare the styles differently in this component and will manage the switch
    * ? to NextJs typings when calling NextJs Image component
    */
    type LayoutValue = 'fixed' | 'intrinsic' | 'responsive' | undefined

    type LayoutAndSize =
    | {
    layout: 'fill'
    }
    | {
    layout: LayoutValue
    height: number
    width: number
    }

    /**
    * Types for the Image component
    * Picking only the props I want to allow, you can adjust to your liking
    */
    type ImageProps = Pick<
    NextImageProps,
    'className' | 'loading' | 'objectFit' | 'objectPosition' | 'priority' | 'quality' | 'src' | 'unoptimized'
    > &
    Pick<Required<NextImageProps>, 'alt'> &
    Pick<ThemingProps, 'variant'> & {
    dimensions?: [number, number]
    layout?: 'fill' | LayoutValue
    }

    /**
    * Wrap NextJs Image component in Chakra's factory function
    * This is what will allow to use the theme and the styling properties on the component
    */
    const ImageWithChakra = chakra(
    ({
    className,
    dimensions = [0, 0],
    layout = 'fill',
    loading,
    objectFit,
    objectPosition,
    priority,
    quality,
    src,
    unoptimized,
    ...nextjsInternals
    }: ImageProps): ReactElement => {
    /**
    * As explained earlier, NextJs typing is preventing auto-suggest for layout, width and height
    * Here we actual convert our component typing to NextJs typing
    */
    const [width, height] = dimensions

    const layoutAndSize: LayoutAndSize =
    height > 0 || width > 0
    ? {
    height,
    layout: layout === 'fill' ? 'intrinsic' : layout,
    width
    }
    : {
    layout: 'fill'
    }

    /**
    * The actual NextImage component
    */
    return (
    <NextImage
    className={className}
    loading={loading}
    objectFit={objectFit}
    objectPosition={objectPosition}
    priority={priority}
    quality={quality}
    src={src}
    unoptimized={unoptimized}
    {...layoutAndSize}
    {...nextjsInternals}
    />
    )
    }
    )

    export const Image = ({ variant, ...props }: ImageProps): ReactElement => {
    /**
    * This components serves as an interface to pass Chakra's styles
    * You can use the theme and/or styling properties (eg. backgroundColor='red.200')
    */
    const styles = useStyleConfig('Image', { variant })
    return <ImageWithChakra sx={styles} {...props} />
    }