Compressing Website Assets

Preface

There are different techniques you can use to optimize page loading times. Minification and combing assets to name a few. The best results can be achieved when combining several techniques. Further down I’ll show you what I use. As a starting point I have my CSS and JS minified by SASS and respectively - UglifyJS. I have this automated with the help of NodeJS and my IDE of choice.

The Resources

Compression is the front-ends’ best friend when it comes to highly optimized page loads times.

Images

Regarding the images probably the most important factor that influences the size of the resulting image is the format. For now there are mainly tree widespread formats (it reads widespread)

  • PNG
  • JPG
  • SVG
  • others, subject of another article

Styles

CSS is the next candidate suitable for compression. It doesn’t matter if you hand-craft you CSS, generate or pre-process it is very compressible even after being minified.

Scripts

JavaScript is the next and last resource that will cover here. Just like the CSS it will compress nicely no matter "pure", generated or minified.

Fonts

Other very commonly used site assets are the Web-fonts. Whether like it or not one have to use all of the available formats to support the majority of the browsers. As a rule of a thumb you will use one of many available font foundries out there so you have not to worry about the compression. So I won’t cover them here.

Those are the resources one will want to compress to gain faster resource transmitting, resulting in faster page load and hopefully better UX. Now let’s take a look at the compression algorithms.

The Compressions

Probably the most widespread compression algorithm is DEFLATE. The other one of the same rank is GZIP. I can’t think of a browser that does not support at least one of them. There of course are others, more exotic algorithms with limited or no browser support. The recently adopted one is brotli. Although, developed by Google the first adopter in production is Firefox. So to wrap them up

  • DEFLATE - widely used and supported amongst all browsers;
  • GZIP - roughly the same support as DEFLATE;
  • brotli - brand new, only release Firefox 44 and Chrome/Chromium behind a runtime configuration flag.

All tree algorithms can compress all of the aforementioned resources with varying compression ratio and speed of compression/decompression.
Algorithms — covered. Now let’s head to the needed tools.

The Tools

Tools for handling DEFLATE and GZIP exists more than 20 years. Those are the well known and used zip and respectively gzip commands of your terminal.
On the other hand the latest brotli specification is from December last year.

If you have spend enough time finding better ways to optimize images from the one Photoshop offers probably have stumbled on zopfli - another Google developed compression algorithm. A quick look at the documentation reveals that it can produce valid DEFLATE and GZIP streams with better compression. What’s the catch? It is slow. This should not bother you since compressing assets naturally is done once on deployment.

After very quick download, compilation and install both bro and zopfli are ready for some crunching. The documentation is clear and easy to follow.
For now we have gzip, zip or zopfli and bro.

Compressing assets

A nicely done tool for automating compression can be found on-line. I use heavily modified version of the script provided at the bottom of the page to make use of the new compression algorithms and adding more resources to compress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/perl

use warnings;
use strict;

use File::Find;

if ($ARGV[0] eq "") {
$ARGV[0] = ".";
}

find(\&wanted, ($ARGV[0]));
sub wanted
{

if (/(.+\.(?:html|css|js|json|svg|png|jpg|gif|ico)$)/) {
my $br = "$_.br";
my $df = "$_.df";
my $gz = "$_.gz";

if (-f $gz) {
if (age ($_) <= age($gz)) {

return;
}
}

# The following substitution is for the case that the file
# name contains double quotation marks ".
$_ =~ s/"/\\"/g;

# Now compress the file.
# system("gzip --keep --best --force \"$_\"");

print($_ . " \r");

# Now compress the file even better.
system("bro --force --input \"$_\" --output \"$br\" --quality 99");
system("zopfli --i1000 -c --deflate \"$_\" > \"$df\"");
system("zopfli --i1000 -c --gzip \"$_\" > \"$gz\"");
}
}

# This returns the age of the file.

sub age
{

my ($file) = @_;
my @stat = stat $file;

return $stat[9];
}

Note: I use quite high compression options and as turns out very time consuming. Presuming that you want to achieve heig compression and the fact that files are compressed once per change justifies the time-consuming operation.

Alright, assets compressed, but will the server handle them?

The Server

Nginx and Apache. You know them well. I use apache for staging/prod and nginx for dev environment. To be frank, I setup only the apache. There are various articles on the Net providing enough info how to setup nginx.
The apache configuration file I use can be found below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<IfModule mod_rewrite.c>

RewriteEngine on
RewriteBase /

# BROTLI
AddEncoding br .br

# If brotli encoding is supported by the browser
# and corresponding version of the file exists
# point the browser to it
RewriteCond %{HTTP:Accept-Encoding} br
RewriteCond %{REQUEST_FILENAME}.br -f
RewriteRule ^(.*)$ $1.br [L]

<FilesMatch \.html\.br$>
ForceType text/html
</FilesMatch>
<FilesMatch \.css\.br$>
ForceType text/css
</FilesMatch>
<FilesMatch \.js\.br$>
ForceType application/javascript
</FilesMatch>
<FilesMatch \.txt\.br$>
ForceType text/plain
</FilesMatch>
<FilesMatch \.json\.br$>
ForceType application/json
</FilesMatch>

<FilesMatch \.svg\.br$>
ForceType image/svg+xml
</FilesMatch>
<FilesMatch \.png\.br$>
ForceType image/png
</FilesMatch>
<FilesMatch \.jpg\.br$>
ForceType image/jpg
</FilesMatch>
<FilesMatch \.gif\.br$>
ForceType image/gif
</FilesMatch>
<FilesMatch \.ico\.br$>
ForceType image/x-icon
</FilesMatch>


# DEFLATE
AddEncoding deflate .df

# If deflate encoding is supported by the browser
# and corresponding version of the file exists
# point the browser to it
RewriteCond %{HTTP:Accept-Encoding} deflate
RewriteCond %{REQUEST_FILENAME}.df -f
RewriteRule ^(.*)$ $1.df [L]

<FilesMatch \.html\.df$>
ForceType text/html
</FilesMatch>
<FilesMatch \.css\.df$>
ForceType text/css
</FilesMatch>
<FilesMatch \.js\.df$>
ForceType application/javascript
</FilesMatch>
<FilesMatch \.txt\.df$>
ForceType text/plain
</FilesMatch>
<FilesMatch \.json\.df$>
ForceType application/json
</FilesMatch>

<FilesMatch \.svg\.df$>
ForceType image/svg+xml
</FilesMatch>
<FilesMatch \.png\.df$>
ForceType image/png
</FilesMatch>
<FilesMatch \.jpg\.df$>
ForceType image/jpg
</FilesMatch>
<FilesMatch \.gif\.df$>
ForceType image/gif
</FilesMatch>
<FilesMatch \.ico\.df$>
ForceType image/x-icon
</FilesMatch>


# GZIP
AddEncoding gzip .gz

# If gzip encoding is supported by the browser
# and corresponding version of the file exists
# point the browser to it
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME}.gz -f
RewriteRule ^(.*)$ $1.gz [L]

<FilesMatch \.html\.gz$>
ForceType text/html
</FilesMatch>
<FilesMatch \.css\.gz$>
ForceType text/css
</FilesMatch>
<FilesMatch \.js\.gz$>
ForceType application/javascript
</FilesMatch>
<FilesMatch \.txt\.gz$>
ForceType text/plain
</FilesMatch>
<FilesMatch \.json\.gz$>
ForceType application/json
</FilesMatch>

<FilesMatch \.svg\.gz$>
ForceType image/svg+xml
</FilesMatch>
<FilesMatch \.png\.gz$>
ForceType image/png
</FilesMatch>
<FilesMatch \.jpg\.gz$>
ForceType image/jpg
</FilesMatch>
<FilesMatch \.gif\.gz$>
ForceType image/gif
</FilesMatch>
<FilesMatch \.ico\.gz$>
ForceType image/x-icon
</FilesMatch>
</IfModule>

<IfModule mod_headers.c>
<FilesMatch ".(html|css|js|json|svg|png|jpg)$">
Header append Vary: Accept-Encoding
</FilesMatch>
</IfModule>

This configuration ensures the highest compression is being served to your users depending on their browser support. It is not based on user agent sniffing rather feature detection.

Epilogue

To some compressing minified files or even images may seem redundant or unneeded but saving even the smallest amounts of traffic to the user can be perceivable not only to them but for your wallet too when serving large amounts of data.

How to install KTouch (Touch Typing Tutor) on Windows

So, you’re on a journey to learn touch type and have heard great things about this KTouch application, but headed straight to Google just to find out it’s running under Linux. Disappointed you’ve searched for a way to install it on Windows or searched for an analog program running in Windows. Naah, that’s so wrong! You can have KTouch running under Windows straight under your fingertips. Here is the proof:
KTouch (Touch Typing Tutor) running under Windows. The keyboard layout is set to Dvorak

How to get your hands on it

It is pretty easy so, let’s just do it.

  1. Go to https://windows.kde.org/ and download the KDE for Windows installer (version KDE SC 4.4.0 at the time of writing);
  2. when downloaded start the installer;
  3. usually the defaults are good enough, but pay some extra attention to the Packages Selection step;
  4. when you reach this step you will have to find the kdeedu package and select it for installation. Just filter out the packages;
  5. complete the installation and you will find the shortcut to the glorious KTouch in your Start menu.

kdeedu marked for install

The shortcut of the installed KTouch under Windows

That’s all of it. Happy touch-typing!

Vertical centering with CSS

Vertically centering a box in a web page is a widespread coding problem. There are many, many solutions out there. Today I’ll present you a modified version of the ‘horizon’ technique.

The ‘horizon’ technique, as I call it, includes an absolutely positioned div to the vertical center of the web page. Inside it’s nested the target box of a desired size. The classic implementation has a certain drawback – if the size of the box is greater than the visible area the browser do not present scrolls. So the content got cropped out. Most of the time this is not a problem. Obviously this is not our case. So let’s dive.

The horizon

As mentioned before the horizon is div absolutely positioned across the page:

1
2
3
4
5
6
#horizon {
position: absolute;
top: 50%;
width: 100%;
height: 1px;
}

And this is what it looks like: step 1. Oh, no! There is a horizontal scroll. This is because we wanted out horizon to span all across the page at 100% not taking into account the margins of the body. Here is the simple fix:

1
2
3
4
5
6
7
8
9
10
body {
padding: 0;
}


#horizon {
position: absolute;
width: 100%;
height: 1px;
top: 50%;
}

Now it looks like it should be: step 2.

This will hold our box at 50% of the height of the body. Now let’s add the box to the scope.

The box

Now that our horizon is ready we need our content container in it. I’ll call it simple ‘scene’:

1
2
3
4
#scene {
width: 750px;
height: 450px;
}

The scene is 750px in width and has 450px height: step 3. As you can assure yourselves the box is not even close to the center. Let alone vertically. Here is how we’ll manage it:

1
2
3
4
5
#scene {
width: 750px;
height: 450px;
margin: -225px auto auto auto;
}

And we’re ready step 4!

Ready

That was really close. All normal browsers (Firefox 2+, Chrome 4+, Opera 7+, Safari 3+) are working fine. Guess who’s wrong? IE6, right. The top part of our box is ruthlessly cut off by the horizon. So far we could do it without hacks. Thankfully, the fix is rather simple. And here it is:

1
2
3
4
5
6
#scene {
width: 750px;
height: 450px;
margin: -225px auto auto auto;
position: relative; /* IE6 fix */
}

I have to note here that IE7+ works just like is expected and renders the page correctly. Here is the final step 5 normalized for IE6.