Marked Responsive Images
An extension for Marked (github, npm) designed to
generate responsive images by parsing simple filename conventions into
full <picture> elements with srcset and
sizes attributes based on simple filename conventions.
Marked Responsive Images parses image filenames to detect available size and file extension variants without breaking standard markdown compatibility.
Installation
npm install marked-responsive-images
Usage
// Default factory export (recommended)
import { marked } from 'marked';
import { markedResponsiveImages } from 'marked-responsive-images';
/*
// or use UMD scripts
<script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked-responsive-images/dist/index.umd.js"></script>
*/
// Register with marked
marked.use(markedResponsiveImages());
// Render markdown
const html = marked.parse('');
Naming Convention
[!TIP]
I have written a PowerShell automation script to automatically generate image size variants, WebP alternatives, and output a fully formed Markedown image link.
Naming the Main File
The extension looks for a specific pattern at the end of your filenames
to generate the <source> tags and/or
srcset attribute.
Pattern:
filename__width-height[-density][-extension]_[…]_currentFileWidth-currentFileHeight.png
- Separator:
Use two underscores (__) to separate the base name from the sizes. - Variants:
Use one underscore (_) to separate different size variants. - Dimensions:
Use a dash (-) to separate width and height. - [optional] Density:
Use a dash (-) followed by a pixel density multiplier (e.g.,1x,1.5x,2x) to instruct the browser to use display density rather than viewport width. - [optional] Extension:
Use a dash (-) to specify a file extension if it is different from the one used by the URL.- Supported formats:
jpg,jpeg,png,webp,avif,gif,svg,jxl.
- Supported formats:
[!NOTE]
The "full name" image must exist on your server.
The image path you write in Markdown (e.g.,hero__400-300_800-600.jpg) is used as the graceful fallback. This raw filename is assigned to thesrcattribute of the inner<img>tag and will be the only image loaded if the extension is disabled or if the Markdown is viewed in an environment that doesn't support responsive images.
[!NOTE]
Format Ordering:
When multiple formats of the same size are provided, the extension automatically sorts the generated<source>tags based on theformatPriorityconfiguration array (defaulting to the most modern/efficient formats first, like JXL and AVIF). The physical order of the tokens in the filename does not matter.
[!IMPORTANT]
This extension does not resize images.
It is your responsibility to ensure that all physical image files—both the "Full Name" fallback and the individual variants (e.g.,hero__400-300.jpg)—actually exist at the destination. This extension only generates the HTML markup to point to them.
Examples
Basic Resizing:
- Markdown:>
 - Resulting HTML:>
<picture> <source srcset="img/photo__400-300.jpg 400w, img/photo__800-600.jpg 800w" type="image/jpeg" /> <img src="https://raw.githubusercontent.com/ELowry/MarkedResponsiveImages/main/img/photo__400-300_800-600.jpg" width="800" height="600" alt="Responsive image example" /> </picture>
Format Switching:
- Markdown:>
 - Resulting HTML:>
<picture> <source srcset="img/photo__800-600.webp 800w" type="image/webp" /> <source srcset="img/photo__800-600.jpg 800w" type="image/jpeg" /> <img src="https://raw.githubusercontent.com/ELowry/MarkedResponsiveImages/main/img/photo__800-600-webp_800-600.jpg" width="800" height="600" alt="Web optimized photo example" /> </picture>
Pixel Density (Retina Displays):
- Markdown:>
 - Resulting HTML:>
<picture> <source srcset="img/ui__400-300.png 1x, img/ui__800-600.png 2x" type="image/png" /> <img src="https://raw.githubusercontent.com/ELowry/MarkedResponsiveImages/main/img/ui__400-300-1x_800-600-2x.png" width="800" height="600" alt="App screenshot" /> </picture>
Configuration
You can configure global options for Marked Responsive Images using:
marked.use(
markedResponsiveImages({
sizes: null, // {string}
class: '', // {string}
pictureClass: '', // {string}
debug: false, // {boolean}
lazy: true, // {boolean}
decoding: 'auto', // {'async' | 'sync' | 'auto'}
renderSimpleImgTags: false, // {boolean}
formatPriority: ['jxl', 'avif', 'webp', 'png', 'jpeg', 'jpg', 'gif', 'svg'], // {Array<string>}
lazyLoadThreshold: 0, // {number}
scoringWeights: null, // { {base?: Record<string, number>, char?: number} | null }
}),
);
| Option | Type | Default | Description |
|---|---|---|---|
|
|
|
|
The |
|
|
|
|
The class attribute to apply to rendered |
|
|
|
|
The class attribute to apply to the |
|
|
|
|
Adds |
|
|
|
|
The |
|
|
|
|
Log warnings to the console when URLs cannot be parsed or formats are malformed. |
|
|
|
|
Enable to generate a simple
When enabled, format variations are automatically stripped out, as standard |
|
|
|
|
Defines the sorting priority for |
|
|
|
|
The visual layout score threshold before lazy loading kicks in. Evaluates the parsed markdown to avoid lazy-loading images "above the fold". A value of |
|
|
|
|
Optionally overrides the engine's default layout estimators. Further details included below. |
Lazy Loading Scoring Threshold Scoring
The lazyLoadThreshold feature works by estimating the
vertical pixel height of your rendered markdown. It reads the
Marked token
stream and assigns a "score" to each element before the HTML
is generated, allowing the extension to skip lazy loading for images
that appear "above the fold."
The engine's default math is based on a simple heuristic: 10 points ≈ 1rem of vertical layout space.
When overriding the scoringWeights option, you can adjust
two properties to match your specific CSS layout:
base:
A dictionary of token types and their base vertical footprint. This accounts for block-level margins, padding, and fixed heights.Example:
If yourtableelements typically have1.5remof top/bottom margin and0.5remof padding, you would assign a base weight of40(4rem total overhead). Fixed-height elements like images bypass character counting completely—a typical 16:9 image capping out at 30rem tall would have a base weight of300.char:
A multiplier applied to the raw text length of leaf nodes (paragraphs, text, code blocks) to estimate wrapped text height.
The formula:(line-height in rem * 10) / characters per lineExample:
If your container wraps text at roughly 80 characters, and your CSSline-heightis1.6rem(16 points), yourcharweight should be16 / 80 = 0.2. Every 5 characters parsed will add 1 point (0.1rem) to the layout score.
Example configuation
marked.use(
markedResponsiveImages({
lazy: true,
lazyLoadThreshold: 800, // Avoids lazy-loading for images that begin within roughly ~80rem
scoringWeights: {
base: {
image: 400, // Estimate images are roughly ~40rem tall instead of the default ~30rem
heading: 60, // Estimate a margin of roughly ~6rem instead of the default ~5rem
},
char: 0.2, // Custom multiplier based on your typography
},
}),
);