search
HomeWeb Front-endJS TutorialDynamic image creation with service workers

Service workers are a fantastic technology. You may know them in relation to the term Progressive Web Application (PWA), so that something that's normally visible on the browser could be "installed" in the OS, and could be opened like a native application, and disinstalled like a native application, and looks like a native application all around. But service workers can do much more than that.

Dynamic image creation with service workers

For accessibility and explanation, look here.

Service workers are basically shared web workers (which exist as a separate technology, by the way) with the special ability to intercept all http requests made by the browser from URLs in the same scope (origin path) the worker has been registered with. Then, it could be instructed to either respond with a constructed or a cached response - actually preventing the browser to hit the network with the request - or pass the request to the network as normal or by modifying the request (using fetch).

This said, it's clear why service workers are often associated with the ability to access to a web page when offline: the first time you can download and cache all the static resources (which basically "installs" the page), then the service worker can respond to the same requests with the cached versions, basically serving the "application resources" like it was a native app. dev.to is a great example of that.

This is already a simplification, and talking about cache busting, updates and the rest is out of scope for this article, so I won't indulge in that. What I'll talk about is the ability of service workers to serve constructed responses.

Mocking responses

My team was recently tasked to build a "showcase" application, i.e. a web application that basically does nothing, but serves the purpose to show how to use our Web Component UI Kit, following the design system and the coding guide lines.

The application was intended as a purely frontend application (meaning we weren't supposed to develop a backend too), but should look like one the many B2B applications that our customer maintains, with backend and all. That's were the role of a service worker comes in handy.

Now, responding with a textual response is quite simple. Even a JSON is basically text, so in the end our service worker could be something like this:

self.addEventListener('fetch', event => {
  if (event.request.url.includes('/api/hello')) {
    event.respondWith(new Response(
      JSON.stringify({ message: 'Hello!' }),
      { headers: { 'Content-Type': 'application/json' }}
    );
  } else  {
    event.respondWith(fetch(event.request));
  }
});

I won't bore you about how this snippet could be improved. URL matching could use URLPattern. You can load static data with fetch and store them on IndexedDB. You can go nuts with that.

But what about other kind of dynamic responses? Like images?

Generating images: the "easy" way.

The easiest way to generate a dynamic image is to create an SVG, which is basically an XML document. Meaning, it's text. It's a totally feasible task, and you can use libraries like D3.js to generate the SVG elements and paths for you: factories like line() and others returns functions that return what you need to put into the d attribute of elements:

self.addEventListener('fetch', event => {
  if (event.request.url.includes('/api/hello')) {
    event.respondWith(new Response(
      JSON.stringify({ message: 'Hello!' }),
      { headers: { 'Content-Type': 'application/json' }}
    );
  } else  {
    event.respondWith(fetch(event.request));
  }
});

Dynamically generating SVGs could be great to get the task off the main thread - and the result could even be cached. This is great for charts and infographics, and "easy" enough to accomplish.

Generating other image types

What's more tricky is generating a raster image like a PNG or a JPG. "Generation" means using editing instruments to alter a picture or create it from scratch. What we usually do in these cases is using a element, getting its 2d context and start painting on it using its many drawing directives.

Problem is, service workers don't have accesso to DOM element. So, are we out of luck?

Worry not, my friends! Because all workers (including service workers) can create OffscreenCanvas objects. Give a width and a height in pixels to the conscructor and there you go, a perfectly fine (although invisible) canvas in a service worker:

import { pie, arc } from 'd3-shape';

const pieData = pie().sort(null)(data);
const sectorArc = arc().outerRadius(35).innerRadius(20);

const svg = '<svg viewbox="-40 -40 80 80" xmlns="http://www.w3.org/2000/svg">'
  + pieData.map((pie, index) =>
    `<path d="${sectorArc(pie)}" fill="${colors[index]}"></path>`
  ).join('')
  + '</svg>';

event.respondWith(new Response(
  svg, { headers: { 'Content-Type': 'image/svg+xml' }}
));

For those wondering: yes, you can get a different type of context, although not all of them are available in every browser. You can try using a library like three.js to generate 3D scenes in a service worker (I think I'll try that later).

Now we can do... whatever, basically. Draw lines, arcs, paths, etc. Even modifying the geometry of our canvas. That's as simple as drawing on a DOM canvas context, so I won't indulge in this part.

Drawing text

We can indeed write text too. This is important because in other environments - namely, a Paint worklet, we cannot do that:

Note: The PaintRenderingContext2D implements a subset of the CanvasRenderingContext2D API. Specifically it doesn’t implement the CanvasImageData, CanvasUserInterface, CanvasText, or CanvasTextDrawingStyles APIs.

But in a service worker, this is all fine. This means that we have a more powerful (although less performant) environment to generate our background images.

Drawing text is as easy as this:

const canvas = new OffscreenCanvas(800, 600);
const context = canvas.getContext('2d');

You can use the font you like here, but I've found that usual standard values like sans-serif, monospace or system-ui don't seem to work, as they all fall back to the default serif font. But you can use font stacks as usual:

context.fillStyle = '#222';
context.font = '24px serif';
// (x, y) = (50, 90) will be the *bottom left* corner of the text
context.fillText('Hello, world!', 50, 90);

Moreover, you can use the Font Loading API to load fonts from external resources:

self.addEventListener('fetch', event => {
  if (event.request.url.includes('/api/hello')) {
    event.respondWith(new Response(
      JSON.stringify({ message: 'Hello!' }),
      { headers: { 'Content-Type': 'application/json' }}
    );
  } else  {
    event.respondWith(fetch(event.request));
  }
});

Sending back to the application

Sending back the response is, again, as easy as calling the convertToBlob method that returns the promise of - you guessed it - a Blob. And blobs can be easily sent back to sender.

import { pie, arc } from 'd3-shape';

const pieData = pie().sort(null)(data);
const sectorArc = arc().outerRadius(35).innerRadius(20);

const svg = '<svg viewbox="-40 -40 80 80" xmlns="http://www.w3.org/2000/svg">'
  + pieData.map((pie, index) =>
    `<path d="${sectorArc(pie)}" fill="${colors[index]}"></path>`
  ).join('')
  + '</svg>';

event.respondWith(new Response(
  svg, { headers: { 'Content-Type': 'image/svg+xml' }}
));

The method creates a PNG image by default, but could be instructed to create a JPG file instead, as seen above. 'image/webp' is another common format, but Safari doesn't support it. To be honest, the choice here is a little underwhelming, as newly available and more capable image format decoders aren't reflected in their corresponding encoders. But that's sufficient for most purposes anyway.

Fun fact: the method convertToBlob is specific to the OffscreenCanvas class. HTMLCanvasElements have toBlob instead, which takes a callback as the first argument, in the common pre-Promise era style of asynchronous task handling.

Using a template image

Now, this all works if we want to create a picture from scratch. But what if we want to start from a blank template?

If we were to work in the main thread, we could slap a picture in the context using the drawImage method of our 2D context, sourcing it e.g. from a readily available Dynamic image creation with service workers element.

Problem is, again, that we can't access the DOM, so we can't reference Dynamic image creation with service workers elements. What we can do, instead, it fetching the picture we need as background, getting its Blob and then convert it to something else that drawImage can digest. Enter createImageBitmap, a global method that's available in service workers too. It returns a promise for an ImageBitmap instance, one of the many less-known classes of frontend web development. It's apparently more widely used in WebGL contexts, but drawImage seems to accept it, so...

const canvas = new OffscreenCanvas(800, 600);
const context = canvas.getContext('2d');

From this point on, we can proceed drawing our scribbles and texts on it, creating a satisfying synamic image to send back to the user.

Note: this could be more easily solved with an SVG, as you could just use a element to set up a background picture. But that would mean the browser has to load the picture after the generated image has been sent, whereas with this technique this is done before. Something similar applies when picking a font.

Putting all together

In all these examples, I've used module service workers (i.e. I've used import from other ES modules). Alas, module service workers aren't yet supported by Firefox, but hopefully they'll be soon. In the meanwhile, you might need to adjust your code to use the old importScripts instead.

When importing other scripts into a service workers, either via import or importScripts, remember that the browser will not fire an updatefound event when an imported file changes: it's fired only when the service worker entry script changes.

In a case like ours, where the service worker is needed only to mock the presence of a backend, its life cycle could be shortcut by calling self.skipWaiting() right when the install event is fired, and then call self.clients.claim() on the activate event in order to be able to immediately respond to requests (otherwise, it'll start only on the next page refresh).

self.addEventListener('fetch', event => {
  if (event.request.url.includes('/api/hello')) {
    event.respondWith(new Response(
      JSON.stringify({ message: 'Hello!' }),
      { headers: { 'Content-Type': 'application/json' }}
    );
  } else  {
    event.respondWith(fetch(event.request));
  }
});

And this is basically everythin, so... have fun with service workers, folks!

The above is the detailed content of Dynamic image creation with service workers. For more information, please follow other related articles on the PHP Chinese website!

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
JavaScript in Action: Real-World Examples and ProjectsJavaScript in Action: Real-World Examples and ProjectsApr 19, 2025 am 12:13 AM

JavaScript's application in the real world includes front-end and back-end development. 1) Display front-end applications by building a TODO list application, involving DOM operations and event processing. 2) Build RESTfulAPI through Node.js and Express to demonstrate back-end applications.

JavaScript and the Web: Core Functionality and Use CasesJavaScript and the Web: Core Functionality and Use CasesApr 18, 2025 am 12:19 AM

The main uses of JavaScript in web development include client interaction, form verification and asynchronous communication. 1) Dynamic content update and user interaction through DOM operations; 2) Client verification is carried out before the user submits data to improve the user experience; 3) Refreshless communication with the server is achieved through AJAX technology.

Understanding the JavaScript Engine: Implementation DetailsUnderstanding the JavaScript Engine: Implementation DetailsApr 17, 2025 am 12:05 AM

Understanding how JavaScript engine works internally is important to developers because it helps write more efficient code and understand performance bottlenecks and optimization strategies. 1) The engine's workflow includes three stages: parsing, compiling and execution; 2) During the execution process, the engine will perform dynamic optimization, such as inline cache and hidden classes; 3) Best practices include avoiding global variables, optimizing loops, using const and lets, and avoiding excessive use of closures.

Python vs. JavaScript: The Learning Curve and Ease of UsePython vs. JavaScript: The Learning Curve and Ease of UseApr 16, 2025 am 12:12 AM

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

Python vs. JavaScript: Community, Libraries, and ResourcesPython vs. JavaScript: Community, Libraries, and ResourcesApr 15, 2025 am 12:16 AM

Python and JavaScript have their own advantages and disadvantages in terms of community, libraries and resources. 1) The Python community is friendly and suitable for beginners, but the front-end development resources are not as rich as JavaScript. 2) Python is powerful in data science and machine learning libraries, while JavaScript is better in front-end development libraries and frameworks. 3) Both have rich learning resources, but Python is suitable for starting with official documents, while JavaScript is better with MDNWebDocs. The choice should be based on project needs and personal interests.

From C/C   to JavaScript: How It All WorksFrom C/C to JavaScript: How It All WorksApr 14, 2025 am 12:05 AM

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.

JavaScript Engines: Comparing ImplementationsJavaScript Engines: Comparing ImplementationsApr 13, 2025 am 12:05 AM

Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

Beyond the Browser: JavaScript in the Real WorldBeyond the Browser: JavaScript in the Real WorldApr 12, 2025 am 12:06 AM

JavaScript's applications in the real world include server-side programming, mobile application development and Internet of Things control: 1. Server-side programming is realized through Node.js, suitable for high concurrent request processing. 2. Mobile application development is carried out through ReactNative and supports cross-platform deployment. 3. Used for IoT device control through Johnny-Five library, suitable for hardware interaction.

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

SublimeText3 Linux new version

SublimeText3 Linux new version

SublimeText3 Linux latest version

Dreamweaver Mac version

Dreamweaver Mac version

Visual web development tools

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

Powerful PHP integrated development environment

SecLists

SecLists

SecLists is the ultimate security tester's companion. It is a collection of various types of lists that are frequently used during security assessments, all in one place. SecLists helps make security testing more efficient and productive by conveniently providing all the lists a security tester might need. List types include usernames, passwords, URLs, fuzzing payloads, sensitive data patterns, web shells, and more. The tester can simply pull this repository onto a new test machine and he will have access to every type of list he needs.

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)