1. 问题背景

在博客文章中,希望通过 Markdown 图片语法同时保存图片地址和图片宽高,例如:

Markdown
![](https://example.com/image.png){width=800 height=450}

目标是让:

  • 后台 Markdown 编辑器能够正确预览图片宽高
  • 数据库存储原始 Markdown 内容
  • 博客前端能够正确解析 width / height
  • 移动端仍然保持响应式,不被固定宽度撑破
  • 后续上传图片时自动写入原始宽高,避免手动填写

实际问题是:后台编辑器右侧预览中,图片可以正常显示,但:

Text
{width=800 height=450}

会被当成普通文本直接显示。


2. 问题原因

标准 Markdown 图片语法只有:

Markdown
![alt](图片地址)

Markdown 标准本身并不支持:

Markdown
{width=800 height=450}

因此下面这种写法:

Markdown
![](https://example.com/image.png){width=800 height=450}

只有在 Markdown 渲染器安装了属性扩展插件之后才能被识别。

如果后台编辑器使用的是 md-editor-v3,其底层使用 markdown-it,所以可以通过 markdown-it-attrs 增加属性解析能力。


3. 推荐的 Markdown 图片格式

统一使用:

Markdown
![图片描述](图片URL){width=原始宽度 height=原始高度}

例如:

Markdown
![丝瓜鸡蛋豆腐汤制作步骤](https://levi-oss-1301066479.cos.ap-guangzhou.myqcloud.com/media/example.png){width=800 height=1200}

建议将图片和属性写在同一行:

Markdown
![](https://example.com/image.png){width=800 height=450}

不要写成:

Markdown
![](https://example.com/image.png)
{width=800 height=450}

虽然部分解析器可以配置支持换行属性,但同一行兼容性更好。


4. 后台编辑器支持方案

4.1 安装插件

如果后台使用 md-editor-v3

Shell
npm install markdown-it-attrs

如果 TypeScript 报类型声明问题,可以尝试:

Shell
npm install -D @types/markdown-it-attrs

如果没有可用类型包,可以手动添加声明文件:

TypeScript
// src/types/markdown-it-attrs.d.ts

declare module 'markdown-it-attrs'

4.2 给 md-editor-v3 注册插件

在项目初始化位置注册:

TypeScript
import { config } from 'md-editor-v3'
import markdownItAttrs from 'markdown-it-attrs'

config({
  markdownItConfig(md) {
    md.use(markdownItAttrs)
  },
})

如果直接在 Vue 组件中使用:

Vue
<script setup lang="ts">
import { MdEditor, config } from 'md-editor-v3'
import markdownItAttrs from 'markdown-it-attrs'

config({
  markdownItConfig(md) {
    md.use(markdownItAttrs)
  },
})
</script>

<template>
  <MdEditor v-model="content" />
</template>

配置后:

Markdown
![](https://example.com/image.png){width=800 height=450}

会被解析成类似:

HTML
<img
  src="https://example.com/image.png"
  width="800"
  height="450"
/>

此时后台右侧预览中不会再把:

Text
{width=800 height=450}

作为普通文字显示。


5. Nuxt 博客前端也必须支持相同语法

后台能正确预览,并不代表博客前端一定能正确解析。

整个链路通常是:

Text
博客后台
   ↓
保存 Markdown
   ↓
Node API / 数据库
   ↓
Nuxt 博客
   ↓
解析 Markdown
   ↓
生成 HTML

因此后台和前台必须使用相同的 Markdown 扩展规则。

如果 Nuxt 前端也是使用 markdown-it

TypeScript
import MarkdownIt from 'markdown-it'
import markdownItAttrs from 'markdown-it-attrs'

const md = new MarkdownIt({
  html: true,
  linkify: true,
  typographer: true,
})

md.use(markdownItAttrs)

否则可能出现:

Text
后台:正常显示图片
前台:图片正常,但下面出现 {width=800 height=450}

6. 图片响应式处理

Markdown 中保存的 widthheight 建议记录图片的原始尺寸。

例如:

HTML
<img width="1280" height="720">

但页面实际展示不要固定为 1280px。

统一增加 CSS:

CSS
.md-editor-preview img,
.article-content img {
  max-width: 100%;
  height: auto;
}

这样效果是:

Text
图片原始尺寸:1280 × 720

桌面端:
最多显示 1280 × 720

手机端:
根据容器自动缩小

这样既保留图片原始比例,又不会撑破移动端布局。


7. 为什么建议保存 width 和 height

保存图片原始宽高不仅用于控制展示效果,还可以减少页面布局抖动。

例如:

HTML
<img
  src="image.png"
  width="1280"
  height="720"
/>

浏览器在图片真正加载完成之前,就已经知道图片宽高比:

Text
1280 / 720 = 16 : 9

因此可以提前为图片预留空间。

优点包括:

  • 减少 CLS(Cumulative Layout Shift)
  • 避免图片加载后把正文突然向下顶
  • 提升文章阅读体验
  • 对 Core Web Vitals 更友好
  • 对 SEO 页面体验指标更友好

8. 不建议把宽高写到图片 URL 中

不推荐:

Markdown
![](https://example.com/image.png?width=800&height=450)

原因是这样会把:

Text
资源地址

和:

Text
展示属性

耦合在一起。

更加合理的是:

Markdown
![](https://example.com/image.png){width=800 height=450}

URL 只负责资源定位,Markdown 属性负责描述图片信息。


9. 推荐:上传图片时自动读取宽高

如果每次都手动写:

Markdown
{width=800 height=450}

操作比较繁琐。

建议后台图片上传流程升级为:

Text
选择图片
   ↓
浏览器读取图片原始宽高
   ↓
上传腾讯云 COS
   ↓
获取 COS URL
   ↓
自动生成 Markdown
   ↓
插入编辑器

最终自动生成:

Markdown
![](COS_URL){width=1280 height=720}

10. 前端读取本地图片尺寸

可以在上传之前通过浏览器读取图片原始尺寸:

TypeScript
function getImageSize(file: File) {
  return new Promise<{
    width: number
    height: number
  }>((resolve, reject) => {
    const img = new Image()
    const objectUrl = URL.createObjectURL(file)

    img.onload = () => {
      resolve({
        width: img.naturalWidth,
        height: img.naturalHeight,
      })

      URL.revokeObjectURL(objectUrl)
    }

    img.onerror = () => {
      URL.revokeObjectURL(objectUrl)
      reject(new Error('读取图片尺寸失败'))
    }

    img.src = objectUrl
  })
}

使用:

TypeScript
const { width, height } = await getImageSize(file)

假设上传图片原始尺寸为:

Text
1440 × 1080

即可得到:

TypeScript
width === 1440
height === 1080

11. 与腾讯云 COS 上传结合

上传逻辑可以整理成:

TypeScript
const { width, height } = await getImageSize(file)

const url = await uploadToCOS(file)

const markdown = `![](${url}){width=${width} height=${height}}`

最终生成:

Markdown
![](https://levi-oss-1301066479.cos.ap-guangzhou.myqcloud.com/media/xxx.webp){width=1440 height=1080}

然后将 markdown 插入到编辑器当前光标位置即可。


12. 推荐的完整实现流程

最终建议博客系统使用下面的处理方式:

Text
┌──────────────────────────┐
│       博客后台编辑器       │
│                          │
│ md-editor-v3             │
│ markdown-it              │
│ markdown-it-attrs        │
└─────────────┬────────────┘
              │
              │ 保存 Markdown
              ▼
┌──────────────────────────┐
│          数据库           │
│                          │
│ 原样保存:                │
│ ![](url){width=...}       │
└─────────────┬────────────┘
              │
              ▼
┌──────────────────────────┐
│         Nuxt 博客         │
│                          │
│ markdown-it              │
│ markdown-it-attrs        │
│ 响应式图片 CSS            │
└──────────────────────────┘

图片上传流程:

Text
选择图片
   ↓
读取 naturalWidth / naturalHeight
   ↓
上传 COS
   ↓
获取图片 URL
   ↓
自动生成

![alt](url){width=xxx height=xxx}
   ↓
插入 Markdown 编辑器

13. 最终推荐规范

博客文章中的所有图片统一使用:

Markdown
![alt](url){width=原始宽度 height=原始高度}

例如:

Markdown
![丝瓜鸡蛋豆腐汤制作步骤](https://levi-oss-1301066479.cos.ap-guangzhou.myqcloud.com/media/1787710705724-article-content-cropped.png){width=800 height=1200}

后台:

Text
md-editor-v3
+ markdown-it-attrs

博客前端:

Text
markdown-it
+ markdown-it-attrs

CSS:

CSS
.article-content img {
  max-width: 100%;
  height: auto;
}

上传时:

Text
自动读取原始宽高
→ 自动生成 Markdown
→ 不需要人工输入尺寸

这样可以一次性解决:

  • Markdown 图片宽高存储
  • 后台编辑器实时预览
  • Nuxt 前端解析
  • 移动端响应式
  • 图片比例保持
  • CLS 页面抖动
  • 图片上传自动化
阅读进度 0%