We’ve all created images in Photoshop that are large, modern looking and definitely on-trend. But what we later come to realize is that the file sizes of these images are astronomical and severely slowing down our apps, providing users with a not-so-great experience. In fact, many will completely abandon a site if it doesn’t render within 3 seconds ... how impatient!

Luckily, there are steps we can take to optimize our images, meaning saving or compiling images in a web friendly format depending on what the image contains.

Unfortunately, optimizing images can be complex, involving resizing, cropping, format conversion and fine-tuning quality parameters. This article explains how you can use Fly Edge Apps to make this process effortless by:

  • Automatically manipulating images based on a user’s device.
  • Improving user experience for all web pages with images.
  • Dramatically improving website performance with little effort.

Resize and Optimize

Your users use a lot of different devices today and expect the websites they visit to load in the blink of an eye. Sometimes it can be tricky to find the right balance between file size and picture quality. You can use Fly Edge Apps to optimize your images before they are even delivered to your user. Ultimately you will deliver the exact images you need for each user without compromising load time. The code sample below shows this method in action.

const optimizedImage = async function(image, width, webp) {
    const response = await fetch(image);
    const contentType = response.headers.get("content-type") || "";
    
    if (!contentType.includes("image/")) return null

    let imageData = await response.arrayBuffer();
    const originalImage = new fly.Image(imageData);
    let newImage = originalImage.resize(width).withoutEnlargement();

    if (webp) {
        newImage = newImage.webp();
        newImage = await newImage.toImage();
    }
    
    return newImage
}

const accept = req.headers.get("accept") || ""
const webp = accept.includes("webp")

const img = await optimizedImage(
    "https://images.pexels.com/photos/1054218/pexels-photo-1054218.jpeg",
    500,
    webp
);

const headers = {
    "content-type": webp ? "image/webp" : "image/jpeg"
}
return new Response(img.data, { headers })

Let’s Break it Down

Let’s break this code down line by line to get a better understanding of what’s exactly going on here.

const optimizedImage = async function (image, width) { ... }

In the first line, we are declaring a function named optimizedImage and passing it two arguments: image, which will be any image URL we want to optimize, and width, which will be the new width this image should be. To make our code work, we need to wrap it inside of an async function. await can only be used in async functions, which allows us to synchronously wait on a promise. (If you use promises outside of async functions, you would then need to use callbacks). Luckily, we’re using the async/await construct so it’s pretty simple - we just add the async keyword before the function declaration.

const response = await fetch(image);

Next, we are awaiting the response of a fetch call and only proceeding once the promise is resolved. fetch() is taking one argument — the URL of the image we want to fetch — and returning a promise containing the response. In this case, we’re fetching whatever image we pass to the function when we invoke it. Instead of continuing to the next line immediately, we wait for the request to finish, hence await. When the request finishes, the resolved value is stored in the response variable for later use.

const contentType = response.headers.get("content-type") || "";

We want to check if the response being passed into the function is in fact an image. We do this by checking the content-type of the response variable.

if (!contentType.includes("image/")) { return null }

If our content-type includes image/, then we know that we are working with an image here-- that’s good! If it’s not an image, our function will end, and nothing will happen ... boring!

let imageData = await response.arrayBuffer();

ArrayBuffer is used to keep binary data – in this case we are keeping the binary data of our image URL. Input and output are much faster using binary data, especially with an image file. So, the binary data of our image URL is being kept in the imageData variable.

const originalImage = new fly.Image(imageData);

A new Image instance is being created and stored in our originalImage variable.

let newImage = originalImage.resize(width).withoutEnlargement();

We are taking the new image instance stored in originalImage and resizing it to whatever width we pass to our function when we invoke it. Our height will automatically be whatever it needs to be to maintain the aspect ratio. For example, if the image is initially 1600x1200, resize(800) will make it 800x600. withoutEnlargement() is saying - do not enlarge the output image if the input image width or height are already less than the required dimensions. We are storing this newly, resized image in our newImage variable.

if (webp) {
    newImage = newImage.webp();
}

WebP is a modern image format that provides compression for images on the web without losing any information. With this method we can create smaller, richer images that make our web pages faster. WebP is natively supported in Google Chrome and the Opera browser, and by many other tools and software libraries. If the get request comes with an accept header, this specifies that the browser supports image/webp. What we are saying here is if the webp argument is true, then convert our image stored in the newImage variable to the WebP format.

newImage = await newImage.toImage();

After the image is resized and converted to the WebP format, it is applying all the changes and then creating a new image instance. Again, we wait for the request to finish before proceeding.

return new Response(newImage.data, { headers });

Finally, we can create an HTTP Response object to return the image to a user. newImage.data is the body, and an init object containing the appropriate headers for the request. Our optimizedImage() function is now ready to be invoked and our optimized image is ready to be served to the browser! See below for an example invoking the function:

optimizedImage("https://images.pexels.com/photos/1054218/pexels-photo-1054218.jpeg", 500);

There you have it! A fully functioning method using Fly Edge Apps to optimize images for your users and serve them to their browsers at the speed of light. Resizing and optimizing images on-the-Fly is just one way that Fly ensures enchanting user experiences and peace of mind for developers.