Code Monkey home page Code Monkey logo

pico's Introduction


๐Ÿ“ธ Pico

Take browser screenshots in Javascript

npm GitHub issues Compressed size

(Original page on the left ยท PNG output on the right)


Goal

Pico's goal is to produce high precision screenshots of any viewport entirely client side. This is different from simply capturing a webpage using Puppeteer or a similar tool in that the screenshot taking happens entirely client side.

The viewport screenshots include scrolled element scroll states, cross-origin images, input states, web fonts, canvas contents, current video frame contents, and much more information that you wouldn't be able to get using something like a headless browser.

At the time of writing there are no existing solutions that are aimed of reproducing the entire viewport accurately like Pico.

How it works

Warning: nerdy

This program renders whatever is displayed in the given Window into an image, thanks to svg's <foreignObject>.

No server side code is required to produce the screenshot.

There is no native Javascript API to take the screenshot of what the user is currently seeing on their screen (and because of security issues there probably will never be one).

Since we don't have access to the raw data that's being shown to the user we have to reconstruct it manually. This program works thanks to svg's <foreignObject> which lets you insert any valid HTML content inside, which we can then pass as a data URL into a <canvas>' drawImage and read out the raw image data with canvas.toBlob or canvas.toDataURL.

The above alone would work great in a universe where subresources didn't exist - which as you know is not our universe. SVG's inserted into <img> tags (or in our case, <canvas>') cannot display any external resources, whether it's images, fonts or stylesheets.

To work around that fact Pico does the following things:

  • Downloads and inlines contents of all <img> tags as data URL's in their src attributes
  • Downloads external stylesheets and inlines them as <style> tags
  • Checks all stylesheets for nested resources
    • Downloads and checks nested stylesheets in @import rules
    • Downloads any resources referenced by the url() function, including but not exclusive to the following properties:
      • backgrounds
      • background-images
      • src in @font-face rule
      • cursor
      • content

In addition, Pico also:

  • Copies input states (text inputs, checkboxes, textareas) into value attributes so that they can be shown in SVG
  • Emulates current scroll positions on all scrolled elements (including the root <html> element) via either transform: translate (for root node) and absolute positioning of children of scrolled nodes
  • Transforms existing <canvas> elements into <img> tags with the contents of the <canvas>' inlined as data URL's in src
  • Performs various minor fixes for rem font size, working media queries, preserving size of everything, etc.

The returned DOM is inserted into an <iframe>, serialized into XML, converted into a data URL, put into an Image, which is then rendered onto a <canvas> whose contents are read out with canvas.toBlob and finally returned to the program's caller, together with all the errors when resources failed to load.

Pico is able to safely accumulate all async resource errors thanks to Fluture, which is a really great alternative to the native Promise and forces you to write type safe errors. You can read a fantastic introductory article to it by the library's author here.

API

Pico is built using Fluture and in addition to the Promise also provides a direct API to Fluture via functions suffixed with Fluture. If you don't care about functional programming just use the non-suffixed functions to work with Promise's instead.

All functions return an "ErrorStack", which is basically just the returned value paired with any errors that happened while computing it. Most errors will be CORS or 404 related issues when loading subresources.

Types

declare type ErrorStack<T> = {
    errors: DetailedError[];
    value: T;
};
export declare type DetailedError = {
    // Human readable string of why the error happened
    reason: string;

    // Proper error object
    error: Error;
};
export declare type Options = {
    // An array of selectors to nodes that should not be included in the output.
    ignore: string[];
};

Functions

declare const objectURL: ($window: Window, partialOptions?: Partial<Options>) => Promise<ErrorStack<string>>;
declare const objectURLFluture: ($window: Window, options: Options) => Fluture<DetailedError, ErrorStack<string>>;

Render the given Window to a PNG image and return it as an object URL. This is safer to use than dataURL due to memory constraints. Remember to call URL.revokeObjectURL when you're done with the image.


declare const dataURL: ($window: Window, partialOptions?: Partial<Options>) => Promise<ErrorStack<string>>;
declare const dataURLFluture: ($window: Window, options: Options) => Fluture<DetailedError, ErrorStack<string>>;

Render the given Window to a PNG image and return it as a data url. Note that in Chrome the limit for data url's is 2MB, prefer objectURL when possible.


declare const svgObjectURL: ($window: Window, partialOptions?: Partial<Options>) => Promise<ErrorStack<string>>;
declare const svgObjectURLFluture: ($window: Window, options: Options) => Fluture<DetailedError, ErrorStack<string>>;

Render the given Window to an SVG image and return it as an object URL. This function is mainly useful for inspecting the output of Pico using devtools, for real uses prefer the other functions.

Installation

$ npm install @gripeless/pico

The module is intended to be used exclusively in the browser via a code bundler like Rollup or Webpack. There is no single file bundle build provided at this time.

Contributing

See contributing.md.

Caveats

Pico is being developed against recent Firefox and Blink based browsers (Chrome, Opera, Brave, Edge). It does not work on Safari or old Edge versions due to lack of proper support for <foreignObject>.

Prior art

Pico's code was inspired in many ways by the following libraries:

Pico's selling point is representing the whole viewport as accurately as possible. If you want to render a single DOM node instead, consider using one of the above libraries.

To the authors of the above code, thank you for your awesome work.

License

MIT

pico's People

Contributors

rsify avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pico's Issues

Inline `url()`'s in `style` attributes

Failing case:

body(style="background-image: url(https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/Stray_kitten_Rambo002.jpg/1200px-Stray_kitten_Rambo002.jpg)")

I imagine not many sites use the url()'s like this, hence this issue is marked as low priority.

Subresource placeholders

Due to browser restrictions, we'll never be able to render foreign <iframe> and tainted <canvas> elements. Because of that we could show a placeholder image in those places instead, e.g. a solid color box with some centered text that describes what was originally on the page there.

Safari support

I read in the description that it doesn't support Safari, but I wonder if Safari has proper foreignObject support now? Is this library still in active development?

Some webfonts not working (only firefox)

On my webapp I have both an icon font (which works everywhere) and some other fonts for text. Both are imported in fontface right after each other, both are local. Only the Icon font is being acurately rendered by pico. This only appears to be an issue on Firefox (76). On Chrome/Chromium my App is being rendered perfectly.

Any idea what's going on here? Really glad to have found this library, since it seems exactly what I was looking for!

Capture cursor position

I would love to have the possibility to have the cursor position recorded in the screenshot.

Here's an example of what it could look like :

Before โ†’ After
wikipedia-capture โ†’ wikipedia-cursor

It could be an optional feature enabled through the options like so :

export declare type Options = {
    // An array of selectors to nodes that should not be included in the output.
    ignore: string[];
	// Display the cursor in the screenshot (default: false)
	showCursor: boolean; 
};

What do you think ?

Gripeless has been discontinued.

Describe the bug
Link to Gripeless in the README says, that "Gripeless has been discontinued"

To Reproduce
Open README

Expected behavior
No mention of Gripeless

Screenshots
Screenshot

Support running animations

Some cutting edge animation libraries (like elm-animator) render the animated UI using generated CSS keyframes, instead of setting properties manually on every frame with requestAnimationFrame.

Currently all animations render out the initial state, which for the described case is completely breaking, and for other smaller cases perhaps unintuitive at the least.

To achieve the desired behavior we need to collapse each animation into frames which could be possible with the new Web Animations API, but I haven't looked into it in much detail.

Option to ignore cloning certain nodes

Sometimes you might want to hide certain parts of the DOM from being rendered out, e.g. Gripeless' SDK embeds a full screen overlay that is not desired to be shown in the image output.

Potential solution would be taking in a CSS selector (or an array of) and removing matched nodes from the cloned DOM tree.


Unfortunately, because we use recursive cloning via document.cloneNode(true) so there'd be no performance benefits other than resources nested under the ignored node not having to be downloaded.

Only download used font files

Taking a screenshot on usegripeless.com needs around 7MB of resources to be downloaded on the first run (latter runs use browser cache) - most of which being imported fonts which the library currently blindly inlines even though 95% of the imported files aren't actually being used.

A potential solution is to use Document.fonts in newer browsers, haven't researched it extra deeply though.

Same origin `<iframe>` rendering

Some apps with micro frontends utilize tons of <iframe> elements to render out their UI.

This is yet to be tested, but as far as I know trying to embed any <iframe> into the exported image will result in a transparent box in place of its contents.

Possible solution would be either to clone the frame via contentWindow/contentDocument or we'd need to make users load a helper library inside the frame and communicate with it via iframe.postMessage.

Proxy failed CORS requests

Many foreign origin CDN's don't set CORS headers when serving their assets (images, style sheets, fonts) which leads to our program not being able to read the asset's contents. For situations like that it seems that the only workaround is falling back onto a simple proxy that will fetch the asset and serve it with friendly CORS headers.

The proxy only needs to be really simple, I haven't thought of exact requirements but this gist looks like a good resource to start with.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.