What squeezr does

Save bytes. Save bandwidth. Save time.

Device-aware
adaptive images

Fit them to your visitors' needs

Images account for the largest share of global web traffic. Websites are viewed with an ever-growing multitude of internet enabled devices, each having it's own characteristics regarding screen size and bandwidth. By tailoring your images to the limitations of your visitors' devices you can save them time and bandwidth and smoothen their web experience.

squeezr recognizes your visitor's screen size, scales your images appropriately, caches and finally delivers them to your visitor.

Server side
CSS3 media queries

Strip out irrelevant CSS

Being the most notable cornerstone of responsive web design, CSS3 media queries have at least one overly significant drawback: While it's their purpose to instruct a device not to apply certain portions of CSS, they still require the device to completely download that very portions. In particular mobile devices with low bandwidth and complex website designs following the Mobile First approach will suffer from this.

squeezr analyses your CSS files and strips out irrelevant media query sections – on the server side.

Test drive squeezr right away

See what it could do for you

squeezr is most effective on sites with big images and heavy use of CSS3 media queries, which is typical for responsive or mobile web designs. However, in most cases at least some small savings can be achieved through image recompression and CSS minification. If the savings calculated by the test pilot aren't that huge, it's either because there's not much to save about your site (e.g. images are too small for downscaling and you don't use media queries), or because you just did a pretty good job already! ;)

Please be aware of the following limitations:

  • The test pilot doesn't find any images loaded or inserted by JavaScript (only <img> elements and CSS background images are evaluated).
  • The test pilot doesn't resolve @import rules in CSS files, they will be ignored.

Please be patient while the test pilot retrieves, analyses and processes your URL. It will attempt to shrink up to 10 (uncompressed) CSS files and the 10 biggest images found at the URL, applying the default settings. In case the test pilot doesn't work properly with your site (although you double-checked typing etc.), please let me know an I will try to fix it.

Objectives & requirements

Modern, fast & flexible

squeezr aims to be a modern and flexible solution and doesn't care much about being too compatible with outdated server environments. It is inteded to be used with HTML5 websites and makes use of data attributes. The overall goal was to leverage the best possible performance in every stage of processing. The techniques involved in squeezr have been choosen and combined deliberately.

However, please be aware that while possibly being a pretty easy to start with and effective approach, squeezr definitely is not a long term solution to the responsive images subject as a whole. Please don't forget to support initatives like the Responsive Images Community Group in developing a proper standard that also gets implemented natively by the browser vendors.

squeezr currently consists of two separate engines that may be used independently of each other:

  1. An image engine for resizing images so that they don't exceed your visitor's screen size.
  2. A CSS engine for removing irrelevant CSS3 media query sections and optionally applying CSS minification.

squeezr is written in modular, object oriented PHP, so it should be easy to implement additional features in the future.

Server requirements
  • Apache Webserver 2.2+ (with mod_rewrite)
  • PHP 5.3+
  • GD (mostly standard with PHP)
Client requirements
  • JavaScript
  • Cookies

Clients not supporting either of both simply won't benefit from squeezr, but your website will still be working for them. They will be delivered the unmodified images and CSS files (this may likely change in the near future).

Server side setup

Getting started with squeezr

Installation

  1. First of all, download the latest version of squeezr from here or from it's GitHub repository and unpack the archive locally on your computer.
  2. If you don't want to use the default setup for some reason (e.g. use a custom squeezr root directory), just modify the common configuration file squeezr/conf/common.php to fit your needs (see here for common configuration options). In case you change the squeezr root directory name (SQUEEZR_DOCROOT) then don't forget to rename the squeezr directory accordingly.
  3. You may as well configure the image and CSS engine at this stage.
  4. Upload the squeezr directory (or whatever you named it) to the top level of your website and ensure that the webserver user has write privileges for this directory.
  5. If your website doesn't have a .htaccess file on the top level yet, simply upload the included one. Otherwise you will have to manually integrate the contents of this file into your existing one. Please have a look at the comments in the file itself for further instructions. You will also find some examples for excluding particular files or directories from squeezing there.
  6. At this point you're almost done – there's only one little step left: setting up the JavaScript in your HTML pages. Once finished with this, squeezr should finally be up and running.

Common configuration

There are some common configuration options that affect all squeezr engines. They are defined as PHP constants inside the file squeezr/conf/common.php, where you can customize them to your needs. These are the existing settings:

SQUEEZR_DOCROOT
You have to provide the absolute file system path to your website root here. squeezr will use this as base component for the construction of every other file or directory path. By default, squeezr uses the $_SERVER['DOCUMENT_ROOT'] value here, which should be okay in most cases.
SQUEEZR_ROOT
This is the absolute path to the squeezr installation directory. By default this is SQUEEZR_DOCROOT/squeezr. Please don't forget to adjust both of the .htaccess files accordingly in case you modifiy this setting.
SQUEEZR_CACHE_LIFETIME
By default, squeezr instructs client browsers to cache any file for one week (604800 seconds). You can change this by providing another expiration period here.

Client side setup

Changes to your HTML markup

Installation

The only markup change squeezr requires you to make is putting a single piece of JavaScript to the <head> section of your HTML pages. The purpose of this script is to measure the client's screen capabilities and write them to a couple of cookies that get sent to the server along with each image and CSS file request. Please observe the following instructions when embedding the squeezr script:

  1. It is very important that you use an inline JavaScript block for incorporating squeezr into your markup. Linking the squeezr script as external resource will likely lead to some clients not reliably setting the cookie values fast enough. Please don't worry too much about the script size – I tried to keep it as small as possible (it only weighs around 1.6 kB), and you'll also benefit from saving an additional HTTP request by embedding it inline.
  2. The <script> element must carry an id attribute with the value squeezr. This is required for the script to reliably identify itself in the DOM.
  3. Copy the contents of the file SQUEEZR_ROOT/squeezr.min.js (the minified source version) into the <script> element. For debugging purposes you might also use the unminified and commented version SQUEEZR_ROOT/squeezr.js, but you shouldn't keep this in a production environment if you care about saving bytes.
  4. If you intend to use the image engine, then the <script> element needs to carry a data-breakpoints-images attribute listing the desired breakpoints for image downscaling (also see below).

The following is a basic example of a valid script tag defining three image breakpoints at 480px, 768px and 1024px:

<script type="text/javascript" id="squeezr" data-breakpoints-images="480,768,1024"> ... </script>

Furthermore, there are another 3 data attributes you might want to use:

data-disable-images
If this attribute is present on the <script> element (regardless of it's value), the image engine is disabled altogether. You don't need to specify the data-breakpoints-images attribute in this case either.
data-disable-css
As you might already expect, the presence of this attribute disables the CSS engine (again, the value doesn't matter, and of course the data-em-precision attribute doesn't make sense then as well). Please note that – as a precaution – either of the engines should be disabled on the server side as well in case you don't want to use it.
data-em-precision
When the squeezr script measures the em-to-pixel ratio of a visitor's screen, it does so by employing a certain precision, which defaults to a value of 0.5. Therefore, squeezr measures in steps like 10em, 10.5em and 11em by default. You can alter this precision by specifying an arbitrary positive floating point value like 0.01, which means that squeezr will be as precise as 10.00em, 10.01em and 10.02em.
data-breakpoints-images
If you want to use the image engine, this attribute has to be present and must carry a comma separated list of breakpoints to be used. The breakpoints have to be expressed in pixels (also have a look at the example above).

Image engine

Scale, cache & deliver

squeezr's image engine is heavily inspired and influenced by Matt Wilcox' Adaptive Images (AI), which you might already be familiar with. The pros and cons of a cookie based approach to adaptive images have been discussed heavily over the past years (e.g. in this excellent series of posts by Jason Grigsby). Almost any of Matt's statements, in particular those regarding the limitations and future prospects of the cookie approach, apply to squeezr as well. (Thanks Matt for your thoughts and this trend-setting piece of code!)

So, when it comes to image adaption, squeezr does pretty much the same as AI. However, I've found AI to have some drawbacks that I wanted to overcome with squeezr (besides integrating the CSS engine), e.g. by employing a different, client side strategy for breakpoint determination.

There are still a couple of things to be done about squeezr's image engine. For example, at the moment it defaults to delivering the original image in case a client doesn't support JavaScript or cookies. I will probably change this very soon to be configurable like in AI, but in the long run I think of an even more flexible approach, possibly involving server side device detection (I've been in touch with the WURFL developers recently ...).

Configuration

There are some configurable options for the image engine located in the file squeezr/conf/image.php (defined as PHP constants). Feel free to change them to your liking:

SQUEEZR_IMAGE
Set this to FALSE to disable the image engine temporarily. To disable it permanently (and prevent PHP from being involved), just remove or comment out the corresponding image rewrite rules from the main .htaccess file.
SQUEEZR_IMAGE_JPEG_QUALITY
Control the quality of JPEG images with this setting. You may specifiy an integer between 1 and 100 (reasonable values are 60 - 80; defaults to 80).
SQUEEZR_IMAGE_SHARPEN
When images are downscaled, they tend to become somewhat blurry. This is why they get slightly sharpened by default. Set this option to FALSE to disable the sharpening.
SQUEEZR_IMAGE_FORCE_SHARPEN
In some situations image sharpening is suspended by default (e.g. when downscaling 8-bit PNG images), as sharpening can seriously affect image quality in these cases. Use this option to forcibly activate sharpening regardless of losses.
SQUEEZR_IMAGE_COPY_UNDERSIZED
Sometimes images don't need to be downscaled for a specific breakpoint as they are already small enough by default. Nevertheless, squeezr creates breakpoint specific symlinks in such cases in order to speed up subsequent requests for the same files. If your system doesn't support symlinks for some reason (e.g. due to PHP restrictions), you can still let squeezr create real copies of those files. Set this option to TRUE to enable this behaviour, but please be aware of the potentially higher disk space requirements.
SQUEEZR_IMAGE_PNG_QUANTIZER
When downscaling 8-bit PNG images, they have to be re-quantized. squeezr comes with an internal quantizer (based on GD), but the results aren't that good. If available, you should use an external quantizer tool like pngquant or pngnq, which has to be installed on the server independently from squeezr (it's up to you to do so). Please see the image engine configuration file to learn about the available options.
SQUEEZR_IMAGE_PNG_QUANTIZER_SPEED
If an external quantizer for PNG images is used (see above), you can – within certain limits – control the processing speed respectively the quality of the resulting images (depending on the quantizer in use). Please see the image engine configuration file for further instructions.

CSS engine

Server side media query revision

When I first learnt about the fact that a CSS3 media query given as inline media attribute like in

<link type="text/css" href="..." media="screen and (min-device-width: 80em)">

doesn't prevent a device from downloading the CSS file – even if it would never satisfy the media query due to hardware limitations – I was really, really disappointed. To a certain extent, this leads the Mobile First approach and progressive enhancement as a principle ad absurdum. Why should the weakest device, having the least capabilities, need to download the full set of CSS, just to discover that significant parts of it will never apply for this very device?

With some of the recent approaches for responsive images in mind I was willing to find a solution for this drawback. In fact, this is the innermost reason for me to come up with squeezr, and having it also deal with images is more a thing of completeness than of ingenuity – there are some pretty capable solutions for this out there (like e.g. Matt Wilcox' Adaptive Images which I mentioned earlier already) that could only be optimized in minute detail, if at all.

squeezr's CSS engine operates very similar to it's image engine: Knowing the client's screen size (submitted via a cookie set by JavaScript), the server parses, analyses, purges and caches CSS files before delivering them to the visitor. Media query sections impossible to be satisfied by the requesting device get stripped out of the CSS, thus reducing the size of data being delivered. Depending on the nature and extent of media query usage this might lead to a significant bandwidth saving especially for the smallest and weakest devices.

Recognized CSS3 media queries

squeezr currently supports only dimensional media queries, which mean media queries implying

  • (min-/max-)device-width
  • (min-/max-)device-height

Dimensions for these features might be specified using px or em as unit. As JavaScript provides a visitor's screen size in px only, squeezr has to translates between em and px internally. Detecting the em-to-pixel ratio of a screen is quite challenging, as the visitor's browser zoom setting may be involved, but squeezr's approach should work fairly well in most cases (I hope at least ;)).

I plan to support more media query features in the future, like e.g. resolution.

Configuration

Also for the CSS engine there are some configurable options you can edit inside the file squeezr/conf/css.php (defined as PHP constants):

SQUEEZR_CSS
Set this to FALSE to disable the css engine temporarily. As with the image engine, to disable it permanently (and prevent PHP from being involved), just remove or comment out the corresponding CSS rewrite rules from the main .htaccess file.
SQUEEZR_CSS_MINIFICATION_PROVIDER
squeezr offers the capability to optionally apply CSS minification. Third party libraries such as Minify may be used as minification providers (in fact, at the time of this writing, Minify is the only supported minification provider yet, but others might be implemented easily due to the modular architecture of squeezr). This setting defaults to the string minify, indicating that minification will be performed using Minify as provider. Set this to NULL to disable minifaction.

How squeezr works

A view under the hood

To give you an idea of what squeezr does in the background when a visitor loads one of your pages, here are the basic steps:

  1. The JavaScript snippet in the <head> section of your page measures your visitor's screen size, display density and em-to-pixel ratio and stores these values in two separate cookies (one for the basic screen properties, one for the CSS breakpoint definition).
  2. Furthermore, the matching breakpoint for image downscaling gets determined by comparing the longer screen side length against the provided list of image downscaling breakpoints. The result is stored in a third cookie. (This is, by the way, one of the main differences compared to Adaptive Images, which does breakpoint determination on the server side.)
  3. When the browser then encounters a <link rel="stylesheet"> or <img> element, it sends the created cookies to the server along with the CSS and image requests.
  4. For both image and CSS requests, the Apache webserver reads the relevant breakpoint definition and internally redirects to an appropriate cached version of the file in question (located somewhere below the SQUEEZR_ROOT/cache directory). The redirect is performed solely by the rewrite rules contained in the top level .htaccess file, and no PHP is involved so far.
  5. If the cached file version already exists, it gets delivered to the user immediately and no further action is taken by the server. If not, the second .htaccess file comes into play and hands the request over to the squeezr hub script. This is the first point PHP gets involved. Based on the incoming request, the hub script decides which engine to invoke.

For files with png, gif or jpg/jpeg extension the image engine operates as follows:

  1. Based on the breakpoint value (as given by the cookie), a cache variant of the requested image is generated. This implies downscaling, optionally sharpening and caching of images that exceed the breakpoint, or simply creating symlinks to the original images in case downscaling is not necessary.
  2. The cached image gets delivered to the visitor. Subsequent requests for the same file and the same breakpoint definition will be directly served by means of the cached image, without any further PHP involvement – even requests from different visitors.

Although the handling of CSS files is somewhat more complex, they are treated in a similar way by the CSS engine:

  1. The requested CSS file is parsed and split into media query and non-media query sections. Next, the CSS engine extracts a list of unique dimensional breakpoint definitions out of the @media rules (up to 64 different breakpoint definitions per CSS file are supported on a 64 bit operating system). Finally all this information is saved as a traversed PHP representation of the stylesheet. This step is only done once for each CSS file, regardless of the visitor's device capabilities. Subsequent requests for the same file will automatically pick up the cached intermediate format.
  2. Then, for the visitor's specific device capabilities, the stylesheet's PHP representation is evaluated. Only non-media query sections as well as media query sections being satisfied by the visitor's device are copied to the output. The resulting and potentially thinned out CSS is cached one last time and finally delivered to the visitor. Subsequent requests matching the same CSS breakpoint definition (also from different clients) will be serverd directly out of the cache without further PHP involvement.

Digging even deeper

Here are some more details that you might be interested in but that didn't fit into any of the other sections.

  • squeezr naturally supports retina displays. The pixel density (as measurable by JavaScript) is part of the image metrics cookie and taken into account when calculating the target dimensions for a cache image.
  • Generated image and CSS cache files are automatically sent to your visitors along with appropriate caching HTTP headers (including e.g. a valid ETag header). While proxy servers are instructed not to store copies of your files, client browsers will be still able to cache your files properly.
  • At the time of this writing, if a visitor's client doesn't support JavaScript or cookies, squeezr simply doesn't have any effect and the original files will be delivered. This behaviour might change soon though, as I think about eventually incorporating server side device detection.
  • When called without any arguments, the squeezr hub script at SQUEEZR_ROOT/index.php acts as a cache cleaning module. Redundant our outdated cache files (both images and CSS) are removed from the cache. In contrast to e.g. Adaptive Images cache cleaning is not part of the regular file request process. You may (and must) implement your own cache cleaning cycle, e.g. by calling the hub script via cronjob or integrating an appropriate script call into your favourite CMS ... Please ket me know if you have some interesting ideas about this! For the TYPO3 folks among you: I came up with a TYPO3 extension for exactly this purpose.

Random thoughts

Some words about the how and why

After all, in case you are interested, let me share with you some thoughts and private motives for making squeezr. As mentioned before, at least regarding the image engine Matt Wilcox' Adaptive Images has played an important role in my decision-making. We had used AI in a couple of projects, we loved it, but I had also found some drawbacks that I wanted to overcome. Every project starts with some basic objectives, so let me outline a couple of mine.

First of all, I really wanted to avoid that every request needs to get processed by a PHP script – especially if an image or CSS file had already been processed and delivered before. By deliberately employing some thoroughly crafted Apache rewrite rules, along with a suitable concept for cache file naming, it should be possible to take the biggest workload off PHP's shoulders. This implies, by the way, a shift of the breakpoint determination from the server to the client (at least for images).

Secondly, it is overly important that even unmodified files (e.g. non-resized images) are sent to the visitor along with proper caching HTTP headers so that the browser may recall the file from it's own cache on subsequent requests. It is too easy to fail on this subject when mangling each request through PHP ...

Furthermore, in my opinion, a cache cleaning mechanism must not be part of the regular file request process, but must be employed externally instead. In fact, the cache validity only changes in that very moment when a source file is changed or deleted, not on every visitor request. It should rather be the modifying system being in charge of cleaning the cache instead of the requesting system. This is why I wanted to leave it up to the implementor to find a solution that is appropriate for his specific environment.

What squeezr doesn't give a proper answer at the moment is the question how clients should be treated that don't support JavaScript and / or cookies. In these cases squeezr simply doesn't have enough information to decide what is best. I believe that this is the point where server side device detection libraries like WURFL could come into play, and I will be experimenting on that. I'd also love to hear your opinion regarding this.

Finally, I'd also like to point you to the cookie race condition problem that quite some of the recent browsers are affected by (also mobile ones!) and that Matt Wilcox has encountered as well. At present there doesn't seem to exist a real solution to this. squeezr contains a test page which you can use for checking your browsers (it's located at example/race-condition.php inside the squeezr package). Regarding squeezr's image engine, the problem will only affect a visitor's very first page load. However, the CSS engine will also be affected each time the visitor's em-to-pixel ratio changes, so e.g. if the visitor sets his page or text zoom factor. Of course it's up to to find these cases overly serious or not ...