helldots 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -0
- package/dist/helldots.esm.js +6 -6
- package/dist/helldots.esm.js.map +4 -4
- package/dist/helldots.umd.js +12 -12
- package/dist/index.d.ts +62 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -12,6 +12,10 @@ widget persist to `localStorage`.
|
|
|
12
12
|
npm install helldots
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
> `modern-screenshot` (the screenshot renderer) is a peer dependency. npm 7+,
|
|
16
|
+
> pnpm 8+ and bun install it automatically; on Yarn, add it explicitly with
|
|
17
|
+
> `yarn add modern-screenshot`.
|
|
18
|
+
|
|
15
19
|
## Quick start
|
|
16
20
|
|
|
17
21
|
```js
|
|
@@ -186,6 +190,95 @@ Three ways out, cheapest first:
|
|
|
186
190
|
- **Leave it.** Captures of such a page stay misaligned where text is
|
|
187
191
|
concerned; everything else about them is correct.
|
|
188
192
|
|
|
193
|
+
### When a capture is slow
|
|
194
|
+
|
|
195
|
+
A screenshot is not a screenshot. The browser does not hand that to
|
|
196
|
+
JavaScript, so the renderer re-creates the page instead: it clones the DOM,
|
|
197
|
+
reads every element's computed style, inlines all of it, serialises the
|
|
198
|
+
result into an SVG `<foreignObject>` and rasterises that.
|
|
199
|
+
|
|
200
|
+
Reading the styles is the render. A browser exposes around 527 computed
|
|
201
|
+
properties per element, and the renderer reads all of them — on a page with
|
|
202
|
+
a few thousand live nodes that is hundreds of thousands of property reads,
|
|
203
|
+
and it is ~91% of the cost. Two things follow, in this order:
|
|
204
|
+
|
|
205
|
+
- **Nothing waits for it.** Clicking or dragging places the marker and opens
|
|
206
|
+
the comment box immediately; the render runs behind it and the images drop
|
|
207
|
+
in when they land. A dragged region shows a "Capturing…" slot in the
|
|
208
|
+
attachment strip until its crop arrives, and Send waits for it if you get
|
|
209
|
+
there first. This is always on; there is nothing to configure.
|
|
210
|
+
- **The page no longer freezes while it happens.** The capture hands the
|
|
211
|
+
main thread back to the browser every 8 ms, so the page keeps painting and
|
|
212
|
+
accepting keystrokes even on a render that runs for a second. Also always
|
|
213
|
+
on — and it is what makes the point above worth anything, since a box you
|
|
214
|
+
can see but cannot type into is not much better than no box.
|
|
215
|
+
- **`fastCapture: true`** narrows those reads to a curated list of the
|
|
216
|
+
properties that change a pixel — measured at ~2.7x off that phase on a
|
|
217
|
+
12 000-element page, and pixel-identical to a full capture on the pages it
|
|
218
|
+
was verified against.
|
|
219
|
+
|
|
220
|
+
`fastCapture` is off by default because the list is a fidelity contract, and
|
|
221
|
+
no list is provably complete for a page this library has never seen: a
|
|
222
|
+
property it does not name is simply absent from the image. Turn it on for a
|
|
223
|
+
heavy page, look at one capture before trusting it, and open an issue if
|
|
224
|
+
something comes out wrong — the fix is one more entry in the list.
|
|
225
|
+
|
|
226
|
+
There is a third lever if your page embeds same-origin iframes.
|
|
227
|
+
**`skipIframeContent: true`** renders them blank instead of cloning what is
|
|
228
|
+
inside. An iframe's cost is invisible from the outside — the renderer walks
|
|
229
|
+
into the frame and clones its whole document, so a page that reports 242
|
|
230
|
+
elements can be a capture of 9 245. Measured at 2374 ms against 82 ms on one
|
|
231
|
+
9 000-node embedded frame.
|
|
232
|
+
|
|
233
|
+
The `<iframe>` element itself is kept: its box, its border, the space it
|
|
234
|
+
occupies. That matters more than it sounds — removing the element instead
|
|
235
|
+
would slide everything below it up by the frame's height while the crop is
|
|
236
|
+
still taken at live page coordinates, which puts the bottom of every capture
|
|
237
|
+
out of register.
|
|
238
|
+
|
|
239
|
+
A cross-origin frame has nothing to gain here. The renderer cannot read into
|
|
240
|
+
it, so it is already blank in the output, and contrary to a common guess it
|
|
241
|
+
does not stall or wait on one either.
|
|
242
|
+
|
|
243
|
+
What none of them touches is rasterisation, which is a single browser
|
|
244
|
+
operation with no JavaScript inside it. On a very long page that stays a
|
|
245
|
+
few hundred milliseconds of unavoidable work.
|
|
246
|
+
|
|
247
|
+
### When one dead asset holds a capture up
|
|
248
|
+
|
|
249
|
+
To inline the page's images and fonts the renderer re-fetches them, and gives
|
|
250
|
+
each one 30 seconds before giving up. A URL that never answers stalls the
|
|
251
|
+
capture until that fires. The capture still succeeds — that asset becomes a
|
|
252
|
+
transparent placeholder — but it waits first.
|
|
253
|
+
|
|
254
|
+
The wait is bounded rather than multiplied: one dead asset and ten cost the
|
|
255
|
+
same, because they are waited on concurrently. What it is not is one times the
|
|
256
|
+
timeout. The setting drives two waits in sequence on the same asset — first
|
|
257
|
+
for the image already on the page to finish loading, then for the fetch that
|
|
258
|
+
inlines it — so the real cost is a consistent ~2x. The 30 second default is
|
|
259
|
+
therefore about a minute.
|
|
260
|
+
|
|
261
|
+
**`captureTimeout: 5000`** cuts that to roughly ten seconds. It is left at
|
|
262
|
+
the default because lowering it trades a slow capture for a silently
|
|
263
|
+
incomplete one: an asset that was only slow, rather than dead, gets dropped
|
|
264
|
+
and leaves a hole with nothing to say so. Since these are assets your page
|
|
265
|
+
has already loaded, most come from cache instantly and the tail is exactly
|
|
266
|
+
the large or uncached ones you would be wrong to drop. Set it if you have
|
|
267
|
+
measured your own page and decided which way you would rather it failed.
|
|
268
|
+
|
|
269
|
+
### Very long pages
|
|
270
|
+
|
|
271
|
+
Browsers cap how large a canvas can be — 65 535 pixels in a dimension in
|
|
272
|
+
Chromium, less in Firefox, and a separate and much lower area cap on mobile
|
|
273
|
+
Safari. A page past that cap cannot be rendered at full scale, so HellDots
|
|
274
|
+
fits the scale to what the browser will actually paint and checks that the
|
|
275
|
+
result holds pixels before using it.
|
|
276
|
+
|
|
277
|
+
Nothing below the cap changes. Past it the capture goes soft in proportion:
|
|
278
|
+
a 68 000px page renders at 0.96, a 140 000px page at 0.47. If even the
|
|
279
|
+
smallest attempt comes back empty, the capture fails through `onError`
|
|
280
|
+
rather than attaching a blank image.
|
|
281
|
+
|
|
189
282
|
### Keeping screenshots out of your database
|
|
190
283
|
|
|
191
284
|
Every image is stored as a base64 data URL inside the record — around 33 KB
|
|
@@ -475,6 +568,9 @@ OS: iOS 17.2
|
|
|
475
568
|
| `persistence` | `"localStorage"` \| `"none"` | `"none"` | Auto save/restore, or handle it yourself via callbacks |
|
|
476
569
|
| `autoScreenshot` | `boolean` | `true` | Capture a screenshot and environment snapshot per comment |
|
|
477
570
|
| `embedCrossOriginFonts` | `boolean` | `false` | Fetch unreadable stylesheets so their web fonts reach the capture |
|
|
571
|
+
| `fastCapture` | `boolean` | `false` | Read a curated style list instead of all ~527 computed properties |
|
|
572
|
+
| `skipIframeContent` | `boolean` | `false` | Render embedded documents as blank instead of cloning them |
|
|
573
|
+
| `captureTimeout` | `number` | `30000` | Milliseconds one remote asset may hold a capture up |
|
|
478
574
|
| `locale` | `string` | browser language | `"en"` and `"es"` ship; anything else falls back per key |
|
|
479
575
|
| `linkParam` | `string` | `"helldotsComment"` | Query param used by "Copy link" URLs |
|
|
480
576
|
| `navigate` | `(page: string) => void` | full page load | SPA router hook for the widget's cross-page jumps |
|