Skip to content

Commit 371228d

Browse files
authored
Add lazy property to pic and annotate
1 parent f0759d3 commit 371228d

File tree

7 files changed

+137
-20
lines changed

7 files changed

+137
-20
lines changed

docs/userGuide/syntax/annotations.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ The x and y coordinates of each Annotate Point are relative to the image and are
1414
<variable name="highlightStyle">html</variable>
1515
<variable name="code">
1616

17-
<annotate src="../../images/annotateSampleImage.png" width="500" alt="Sample Image">
17+
<annotate src="../../images/annotateSampleImage.png" width="500" alt="Sample Image" lazy>
1818
<!-- Minimal Point -->
1919
<a-point x="25%" y="25%" content="This point is 25% from the left and 25% from the top" />
2020
<!-- Customize Point Size (default size is 40px) -->
@@ -191,11 +191,12 @@ Here we showcase some use cases of the Annotate feature.
191191
This is effectively the same as the options used for the [picture](#pictures) component.
192192

193193
| Name | Type | Default | Description |
194-
| ------ | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
194+
|--------| --------- | ------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
195195
| alt | `string` | | **This must be specified.**<br>The alternative text of the image. |
196196
| src | `string` | | **This must be specified.**<br>The URL of the image.<br>The URL can be specified as absolute or relative references. More info in: _[Intra-Site Links]({{baseUrl}}/userGuide/formattingContents.html#intraSiteLinks)_ |
197197
| height | `string` |`''`| The height of the image in pixels. |
198198
| width | `string` |`''`| The width of the image in pixels.<br>If both width and height are specified, width takes priority over height. It is to maintain the image's aspect ratio. |
199+
| lazy | `boolean` | false | The `<annotate>` component lazy loads if this attribute is specified.<br>**Either the height or width should be specified to avoid layout shifts while lazy loading images.** |
199200

200201
</div>
201202

docs/userGuide/syntax/pictures.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<include src="codeAndOutput.md" boilerplate >
66
<variable name="highlightStyle">html</variable>
77
<variable name="code">
8-
<pic src="https://markbind.org/images/logo-lightbackground.png" width="300" alt="Logo">
8+
<pic src="https://markbind.org/images/logo-lightbackground.png" width="300" alt="Logo" lazy>
99
MarkBind Logo
1010
</pic>
1111
</variable>
@@ -18,11 +18,12 @@ alt | `string` | | **This must be specified.**<br>The alternative text of the im
1818
height | `string` | | The height of the image in pixels.
1919
src | `string` | | **This must be specified.**<br>The URL of the image.<br>The URL can be specified as absolute or relative references. More info in: _[Intra-Site Links]({{baseUrl}}/userGuide/formattingContents.html#intraSiteLinks)_
2020
width | `string` | | The width of the image in pixels.<br>If both width and height are specified, width takes priority over height. It is to maintain the image's aspect ratio.
21+
lazy | `boolean` | false | The `<pic>` component lazy loads if this attribute is specified.<br>**Either the height or width should be specified to avoid layout shifts while lazy loading images.**
2122

2223
<div id="short" class="d-none">
2324

2425
```html
25-
<pic src="https://markbind.org/images/logo-lightbackground.png" width="300" alt="Logo">
26+
<pic src="https://markbind.org/images/logo-lightbackground.png" width="300" alt="Logo" lazy>
2627
MarkBind Logo
2728
</pic>
2829
```

packages/core/src/html/NodeProcessor.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,20 @@ export class NodeProcessor {
265265
*/
266266
if (!_.has(node.attribs, 'v-pre')) { node.attribs['v-pre'] = ''; }
267267
break;
268+
case 'pic':
269+
case 'annotate':
270+
if (_.has(node.attribs, 'lazy')
271+
&& !(_.has(node.attribs, 'width') || _.has(node.attribs, 'height'))) {
272+
const filePath = context.callStack.length > 0 ? context.callStack[context.callStack.length - 1]
273+
: context.cwf;
274+
logger.warn(
275+
`${filePath} --- `
276+
+ 'Both width and height are not specified when using lazy loading in the file and'
277+
+ ' it might cause shifting in page layouts. '
278+
+ 'To ensure proper functioning of lazy loading, please specify either one or both.\n',
279+
);
280+
}
281+
break;
268282
default:
269283
break;
270284
}

packages/core/test/unit/html/NodeProcessor.test.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import path from 'path';
22
import cheerio from 'cheerio';
33
import htmlparser from 'htmlparser2';
4-
import * as testData from './NodeProcessor.data';
4+
import { expect } from '@jest/globals';
55
import * as logger from '../../../src/utils/logger';
6+
import * as testData from './NodeProcessor.data';
67
import { Context } from '../../../src/html/Context';
78
import { shiftSlotNodeDeeper, transformOldSlotSyntax } from '../../../src/html/vueSlotSyntaxProcessor';
89
import { getNewDefaultNodeProcessor } from '../utils/utils';
@@ -139,6 +140,68 @@ test('processNode processes dropdown with header slot taking priority over heade
139140
expect(warnSpy).toHaveBeenCalledWith(testData.PROCESS_DROPDOWN_HEADER_SLOT_TAKES_PRIORITY_WARN_MSG);
140141
});
141142

143+
test('processNode does not log warning when lazy pic has width or height',
144+
() => {
145+
const nodeProcessor = getNewDefaultNodeProcessor();
146+
147+
const testCode = '<pic scr="" alt="" width="300" lazy></pic>';
148+
const testNode = parseHTML(testCode)[0] as MbNode;
149+
150+
const consoleSpy = jest.spyOn(logger, 'warn');
151+
152+
nodeProcessor.processNode(testNode, new Context(path.resolve(''), [], {}, {}));
153+
154+
expect(consoleSpy).not.toHaveBeenCalled();
155+
});
156+
157+
test('processNode does not log warning when lazy annotate has width or height',
158+
() => {
159+
const nodeProcessor = getNewDefaultNodeProcessor();
160+
161+
const testCode = '<annotate scr="" alt="" height="300" lazy></annotate>';
162+
const testNode = parseHTML(testCode)[0] as MbNode;
163+
164+
const consoleSpy = jest.spyOn(logger, 'warn');
165+
166+
nodeProcessor.processNode(testNode, new Context(path.resolve(''), [], {}, {}));
167+
168+
expect(consoleSpy).not.toHaveBeenCalled();
169+
});
170+
171+
test('processNode logs warning when lazy pic no width and height',
172+
() => {
173+
const nodeProcessor = getNewDefaultNodeProcessor();
174+
175+
const testCode = '<pic scr="" alt="" lazy></pic>';
176+
const testNode = parseHTML(testCode)[0] as MbNode;
177+
178+
const consoleSpy = jest.spyOn(logger, 'warn');
179+
180+
nodeProcessor.processNode(testNode, new Context('testpath.md', [], {}, {}));
181+
182+
expect(consoleSpy).toHaveBeenCalledWith('testpath.md --- '
183+
+ 'Both width and height are not specified when using lazy loading in the file and'
184+
+ ' it might cause shifting in page layouts. '
185+
+ 'To ensure proper functioning of lazy loading, please specify either one or both.\n');
186+
});
187+
188+
test('processNode logs warning when lazy annotate no width and height',
189+
() => {
190+
const nodeProcessor = getNewDefaultNodeProcessor();
191+
192+
const testCode = '<annotate scr="" alt="" lazy></annotate>';
193+
const testNode = parseHTML(testCode)[0] as MbNode;
194+
195+
const consoleSpy = jest.spyOn(logger, 'warn');
196+
197+
nodeProcessor.processNode(testNode, new Context('testpath.md', [], {}, {}));
198+
199+
expect(consoleSpy).toHaveBeenCalledWith('testpath.md --- '
200+
+ 'Both width and height are not specified when using lazy loading in the file and'
201+
+ ' it might cause shifting in page layouts. '
202+
+ 'To ensure proper functioning of lazy loading, please specify either one or both.\n');
203+
});
204+
142205
test('markdown coverts inline colour syntax correctly', async () => {
143206
const nodeProcessor = getNewDefaultNodeProcessor();
144207
const indexPath = 'index.md';

packages/vue-components/src/Pic.vue

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
:src="src"
66
:alt="alt"
77
:width="computedWidth"
8+
:height="computedHeight"
9+
:loading="computedLoadType"
810
class="img-fluid rounded"
9-
@load.once="computeWidth"
11+
@load.once="computeWidthAndHeight"
1012
/>
1113
<span class="image-caption">
1214
<slot></slot>
@@ -35,6 +37,10 @@ export default {
3537
type: String,
3638
default: '',
3739
},
40+
lazy: {
41+
type: Boolean,
42+
default: false,
43+
},
3844
addClass: {
3945
type: String,
4046
default: '',
@@ -53,20 +59,30 @@ export default {
5359
}
5460
return this.widthFromHeight;
5561
},
62+
computedHeight() {
63+
return this.heightFromWidth;
64+
},
65+
computedLoadType() {
66+
return this.lazy ? 'lazy' : 'eager';
67+
},
5668
},
5769
data() {
5870
return {
5971
widthFromHeight: '',
72+
heightFromWidth: '',
6073
};
6174
},
6275
methods: {
63-
computeWidth() {
64-
if (!this.hasWidth && this.hasHeight) {
65-
const renderedImg = this.$refs.pic;
66-
const imgHeight = renderedImg.naturalHeight;
67-
const imgWidth = renderedImg.naturalWidth;
68-
const aspectRatio = imgWidth / imgHeight;
76+
computeWidthAndHeight() {
77+
const renderedImg = this.$refs.pic;
78+
const imgHeight = renderedImg.naturalHeight;
79+
const imgWidth = renderedImg.naturalWidth;
80+
const aspectRatio = imgWidth / imgHeight;
81+
if (this.hasWidth) { // if width is present, overwrite the height (if any) to maintain aspect ratio
82+
this.heightFromWidth = Math.round(toNumber(this.width) / aspectRatio).toString();
83+
} else if (this.hasHeight) {
6984
this.widthFromHeight = Math.round(toNumber(this.height) * aspectRatio).toString();
85+
this.heightFromWidth = this.height;
7086
}
7187
},
7288
},

packages/vue-components/src/__tests__/__snapshots__/Annotation.spec.js.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ exports[`Annotation with customised annotation points 1`] = `
66
>
77
<img
88
class="annotate-image"
9+
height=""
10+
loading="eager"
911
src="./annotateSampleImage.png"
1012
width=""
1113
/>
@@ -92,6 +94,8 @@ exports[`Annotation with different visual annotation points 1`] = `
9294
>
9395
<img
9496
class="annotate-image"
97+
height=""
98+
loading="eager"
9599
src="./annotateSampleImage.png"
96100
width=""
97101
/>
@@ -380,6 +384,8 @@ exports[`Annotation with markdown in header, content and label 1`] = `
380384
>
381385
<img
382386
class="annotate-image"
387+
height=""
388+
loading="eager"
383389
src="./annotateSampleImage.png"
384390
width=""
385391
/>

packages/vue-components/src/annotations/Annotate.vue

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
:src="src"
66
:alt="alt"
77
:width="computedWidth"
8+
:height="computedHeight"
9+
:loading="computedLoadType"
810
class="annotate-image"
9-
@load.once="getWidth"
11+
@load.once="computeWidthAndHeight"
1012
/>
1113
<div style="top: 0; left: 0; height: 0;">
1214
<slot></slot>
@@ -35,6 +37,10 @@ export default {
3537
type: String,
3638
default: '',
3739
},
40+
lazy: {
41+
type: Boolean,
42+
default: false,
43+
},
3844
addClass: {
3945
type: String,
4046
default: '',
@@ -53,20 +59,30 @@ export default {
5359
}
5460
return this.widthFromHeight;
5561
},
62+
computedHeight() {
63+
return this.heightFromWidth;
64+
},
65+
computedLoadType() {
66+
return this.lazy ? 'lazy' : 'eager';
67+
},
5668
},
5769
data() {
5870
return {
5971
widthFromHeight: '',
72+
heightFromWidth: '',
6073
};
6174
},
6275
methods: {
63-
getWidth() {
64-
if (!this.hasWidth && this.hasHeight) {
65-
const renderedImg = this.$refs.pic;
66-
const imgHeight = renderedImg.naturalHeight;
67-
const imgWidth = renderedImg.naturalWidth;
68-
const imageAspectRatio = imgWidth / imgHeight;
69-
this.widthFromHeight = Math.round(toNumber(this.height) * imageAspectRatio);
76+
computeWidthAndHeight() {
77+
const renderedImg = this.$refs.pic;
78+
const imgHeight = renderedImg.naturalHeight;
79+
const imgWidth = renderedImg.naturalWidth;
80+
const aspectRatio = imgWidth / imgHeight;
81+
if (this.hasWidth) { // if width is present, overwrite the height (if any) to maintain aspect ratio
82+
this.heightFromWidth = Math.round(toNumber(this.width) / aspectRatio).toString();
83+
} else if (this.hasHeight) {
84+
this.widthFromHeight = Math.round(toNumber(this.height) * aspectRatio).toString();
85+
this.heightFromWidth = this.height;
7086
}
7187
},
7288
},

0 commit comments

Comments
 (0)