nuxt / image

Plug-and-play image optimization for Nuxt applications.
https://image.nuxt.com
MIT License
1.33k stars 270 forks source link

$img to replace images in markdown with widths not bigger than original #389

Open msoler75 opened 3 years ago

msoler75 commented 3 years ago

Hi, i'm using markdown content with images like

# title
![](/uploads/image1.jpg)
![](/uploads/image2.jpg)

I render that markdown using @nuxtjs/markdownit and placing in template like

<div v-html="content.htmlOutput" />

Before that, I wanted to prepare all regular images to get the responsive benefits using $img

I put all this process into asyncData function to get fastest response

async asyncData({ app, $md, $img, $strapi, route, redirect }) {
 // Fetch content here
 const content = ...
 // use @nuxtjs/markdownit here to get html version
 var html = $md.render(content.markdown)
 html = html.replace(/<img[^>]+>/g, (p0) =>
      {
        const m = p0.match(/src=['"]([^'"]+)['"]/)
        if(!m||!m[1]) return p0
        // here we could calculate proper 'sizes' for this image
        const SIZES = 'xs:100vw xm:100vw sm:100vw md:100vw lg:100vw'
        const src = m[1]
        const img = $img.getSizes(src, {
          sizes: SIZES,
          modifiers: {
            format: 'webp',
            quality: 90
          }})    
        return  `<img
        src="${$img(src, { quality: 70 })}"
        srcset="${img.srcset}"
        sizes="${img.sizes}"
      >`
      })
    content.htmlOutput = html
    return {content}
}

There is some things here:

¿is there any better way to do this?

pi0 commented 3 years ago

Hi @msoler75. There is an open issue (#268) to avoid generating unnecessary URLs (important: for this to work, you need to pass width and height to $img API otherwise we don't know about the original dimensions) also from Server/IPX side, we automatically prevent upscaling even if URL says 640px and actual image is 400px it won't be upscaled (https://github.com/unjs/ipx/pull/41 based on original image file. props to @ascorbic)

msoler75 commented 3 years ago

I resolved with this code, for now:

// I get image dimensions {width, height} here
// const dimensions = (...)
const opts = {
    format: "webp",
    quality: 80
};
const screens = {
  xs: 320,
  sm: 640,
  md: 768,
  lg: 1024
  xl: 1280
};
var sizes = [];
for (const s in screens) {
  if (dimensions.width >= screens[s]) 
    sizes.push(`${s}:100vw`);
  else {
    sizes.push(`${s}:${dimensions.width}px`);
     break;
   }
}
sizes = sizes.join(" ");
opts.width = dimensions.width; opts.height = dimensions.height;
const imgr = $img.getSizes(src, {
    sizes,
    modifiers: opts
});

return `<img
            loading="lazy"
            src="${$img(src, { quality: 70 })}"
            srcset="${imgr.srcset}"
            sizes="${imgr.sizes}"
          >`;
salzig commented 10 months ago

Hej, I didn't liked the html.replace way of handling it. But needed a way to render Markdown with MarkdownIt that containes Links to Images. So I had to "replace"/"map"/"wrap" those images manually with Nuxt-image.

my solution was to use the following on a component that had a input-property and a template of <div v-html="richText"></div>

  computed: {
    richText() {
      const replaceImageSrc = (tokens) => {
        for (const token of tokens) {
          if (token.tag === "img") {
            token.attrSet('src', this.$img(token.attrGet('src')))
          }
          if (token.children) {
            replaceImageSrc(token.children)
          }
        }
      }

      const rule = (state) => {
        replaceImageSrc(state.tokens)
      }

      if (this.$md.core.ruler.__find__("nuxt-image") == -1) {
        this.$md.core.ruler.push("nuxt-image", rule)
      }

      return this.$md.render(this.input)
    }
  },

Hope that is some inspiration for someone else.