Use of css sprite

Prepare your css sheet in an image editor. The final format will be in png to keep your images as sharp as possible, webp is also a good candidate if you take care of the brower support.

When laying out your images on the css sprite image, make sure to use consistent spacing between the images. It will help when styling your sheet and keeping it minimal.

You indicate the size of the portion of the picture of the sprite you want to show in the css code. If you want to reuse the height and width of your styling as much as possible, make sure that the image are at least height and width apart from each other (measure from their top left corner as this is the coordinate you will use to display the proper part of the CSS sprites.

Note that this is a cylinder system of coordinate which means if the height you set is larger than the height left between the top left corner of your image and the bottom of the sprite sheet, the remnant will be carried over at the top of the sprite and everything within that value will be displayed. In other words, the sprite repeats itself until it covers the entire height you set.

It works in a similar pattern for the width.

As mentioned above, you indicate what part of the sprite to display by entering the coordinate of the top left corner of your image on the sprite. The top left corner of the sprite is the origin of the coordinate system. Along the top of your image you will have the y axis and along the left side of your image you will have the x axis. So every point in your image will have negative coordinate.

I choosed x and y, because the coordinate along the height of the image will be entered first while the coordinate along the width, second. This match the usual (x,y) way of representing 2D coordinate so that’s why I use this terminology.

Create and hold your private key securely.

The best way to securely create and hold your private key is to do so on an encrypted drive. In that way only you will have access to the data even in the event your drive get lost or stolen.

Why creating a private key directly on an encrypted drive is more secure?

In case you stored your private key on a non encrypted drive, keep in mind that, even if you delete the key from the drive, the data will still be accessible through data recovery process. The only way to make sure the data is not accessible after you erase them is to write zero to the drive, which means you will lose any other data stored on that drive in the process.

That’s the main reason why you should create your private key securely directly on an encrypted drive and don’t move them around. Backup of the key should be also held on an encrypted drive.

To generate your SSL key pair, download and install openSSL.

Generating self signed certificate:

On a windows machine after installing and setting up openssl:

run cmd

cd C:\OpenSSL-Win32\bin

change the path after the cd command to your OpenSSL installation path

openssl genrsa -des3 -out server.key 4096
enter pass phrase for server.key:

Important: enter a password here, nothing will be displayed on screen, you will be ask to confirm this password that will be used during the next step

openssl req -config C:\OpenSSL-Win32\bin\cnf\openssl.cnf -new -key server.key -out server.csr 
enter pass phrase for server.key:

This is the pass phrase you created at the previous step.

Important: leave challenge password blank, virtuemart won’t be able to validate the certificate if anything is entered as chanllenge password. Leave it blank.

Generate certificate:

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt 

For 2048 bits long certificate so first line become:

openssl genrsa -des3 -out server.key 2048

Flexbox

Flexbox is perfect to manage nice and responsive layout in modern browser. Let’s see how we can integrate it with horme 3 template for virtuemart, together with lazyloading.

Our first implementation does not give desirable result.

The step I took so far:

1- remove matchHeight script

2- change the row div to a flex-container class

3- change the product column

<div class="product vm-col<?php echo ' vm-col-' . $products_per_row . ' ' . $cellwidth ;?>">

to

<div class="product flex-item>" > 

The issue is that there is only 3 items per row, leaving an empty column. The add to cart button seems to go all over the place as soon as the third row is reached.

The add to cart button need to be pushed at the bottom of the div. It seems the div height change depending on the scrolling. Everything is fine when the css load but if scrolling down then up, then the div might be displayed with a “too short” height, hiding the add to cart button in the process.

@media query not working under certain screen size

Issue: CSS @media query are not working under a certain width (in my case it was under about 900px).

@media screen and (max-width: 600px) {
  body {
    background-color: olive;
    color: white;
  }

Solution: check that you have set the view port in your page head meta:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

Implementing lazy loading in Virtuemart

[UPDATE] Now joomla 4 uses native lazy loading thanks to the loading attribute.

Exemple:

<img src="image.png" loading="lazy" alt="…" width="200" height="200">

https://github.com/joomla/joomla-cms/issues/27761

Let’s start this a quick refresh on lazy loading:

https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video/

To implement lazy loading (with this method) in virtuemart category page, you will need to add a class to the thumbnail <img> element, which is easily done in /com_virtuemart/sublayouts/products.php

Look for the line:

echo $product->images[0]->displayMediaThumb('class="browseProductImage"', false);

and add the desired class to the displayMediaThumb() function:

echo $product->images[0]->displayMediaThumb('class="browseProductImage lazy"', false);

In your theme template, create an override for the index.php and add the javascript in the footer:

 document.addEventListener("DOMContentLoaded", function() {
  var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));

  if ("IntersectionObserver" in window) {
    let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          let lazyImage = entry.target;
          lazyImage.src = lazyImage.dataset.src;
          lazyImage.srcset = lazyImage.dataset.srcset;
          lazyImage.classList.remove("lazy");
          lazyImageObserver.unobserve(lazyImage);
        }
      });
    });

    lazyImages.forEach(function(lazyImage) {
      lazyImageObserver.observe(lazyImage);
    });
  } else {
    // Possibly fall back to a more compatible method here
  }
}); 

At this stage your code won’t work and no image will be displayed. For the code to work you need to have a data-src defined in the <img> element.

One way to do this is to locate where the <img> html for the thumbnail is generated and create an override to define a data-src instead of the usual src.

I didn’t find any documentation as to where this code is generated, so I add to search for it. I used eclipse IDE and added a local copy of my virtuemart installation as a workspace in the IDE then search the function in the folder. It was located in:

/administrator/components/com_virtuemart/helpers/mediahandler.php

The html per say is generated by the displayIt() function. We change src to data-src in the file for the moment as it is not clear to me how I’ll be able to override this function in my theme.

My first test after settings all this is not running properly. Actually it does even seem to act the opposite it should, with the picture first loaded and that disappear when it crawls into view.

Let’s debug that. First, I checked that all my code is properly loaded, namely the class for the image and the data-src to prevent the picture to load before it come into view.

At that stage the “lazy” class is properly added to the source code but the image source is not changed to data-src as expected.

Changing this in the developer console does not solve the issue (picture still not showing) unless the srcset=”undefined” is deleted. This srcset attribute is added by the lazyload js.

By searching for <img in the mediahandler.php file I found the function displayImage(). I changed the src attribute to data-src and remove the srcset line from the lazyload javascript before running another test.

Changed src to data-src in evey img element in displayImage() and displayIt() function. Now the lazy loading function is working except for some css issue (product name appearing on overlay on the image instead of displaying below the image). Some pictures do not appear at all.

Product img elements have now data-src attribute instead of src but , they don’t have the “lazy” class so they are not displayed when they come into the view port. This might be changed in the single product view.

Product image has no class set but they don’t load. The lazy load script should not prevent image without the lazy class to load, so the issue might be that I changed the src attribute to data-src.

So the displayIt() function in /administrator/components/com_virtuemart/helpers/mediahandler.php handles picture in the product view as well as in the category view.

To add the lazy class to the product image and additional thumbnails, we need to modify default_images.php and default_images_additional.php in templates/your_template_name/html/com_virtuemart/productdetails/

in defaut_images.php look for the following code:

if(!empty($width) or !empty($height)){

            echo $image->displayMediaThumb("",true,"rel='vm-additional-images' class='block'", true, true, false, $width, $height);

        } else {

            echo $image->displayMediaFull("",true,"rel='vm-additional-images' class='block'");

        }

and change it to:

if(!empty($width) or !empty($height)){
echo $image->displayMediaThumb("class='lazy'",true,"rel='vm-additional-images' class='block'", true, true, false, $width, $height);
} else {
echo $image->displayMediaFull("class='lazy'",true,"rel='vm-additional-images' class='block'");
}

And in default:images_additional.php

 if(VmConfig::get('add_img_main', 1)) {
            echo $image->displayMediaThumb('class="product-image thumbnail" style="cursor: pointer"',false,false);
            echo '';
        } else {
            echo $image->displayMediaThumb("class='thumbnail'",true,"rel='vm-additional-images'",true,false);
        }

becomes:

if(VmConfig::get('add_img_main', 1)) {
            echo $image->displayMediaThumb('class="product-image thumbnail lazy" style="cursor: pointer"',false,false);
            echo '';
        } else {
            echo $image->displayMediaThumb("class='thumbnail lazy'",true,"rel='vm-additional-images'",true,false);
        }

Note: that you must override these two files in your template if you don’t want to see your changes deleted by the next update.

At that point we are left with the issue that thumbnails div does not have the appropriate height which leads to the product name to overlay on the picture, which in our case is not desirable. It seems to be handled in the products.php sublayouts and the class of the div we are looking for is vm-product-media-container. The div is indeed located in this file but it does not have the style attribute that causes the issue, nor does it call a variable for this, that suppose that the inline style is added through a filter on the server side or through js or jquery on the client side.

In our case, the following inline style is injected and cause display issue: style=”height: 42px;”

 Line 1165  $imgHeight = VmConfig::get('img_height','');

        if(!empty($imgHeight)){

            $imgHeight = 'height:'.VmConfig::get('img_height',90).'px;';

        } else {

            $imgHeight = '';

        }

To be specific the issue in on the div wrapping the image:

<div class=”vm-product-media-container” data-mh=”media-container” style=”height: 63px;”>

This code is generated in : /html/com_virtuemart/sublayouts/products.php

function displayMediaThumb($imageArgs='',$lightbox=true,$effect="class='modal' rel='group'",$return = true,$withDescr = false,$absUrl = false, $width=0,$height=0)

Let’s see where the display MediaThumb() is called and find out where the 42px height is defined.

in default_images.php

if (!empty($this->product->images)) {
    $image = $this->product->images[0];
?>
displayMediaThumb("",true,"rel='vm-additional-images'", true, true, false, $width, $height);
} else {
echo $image->displayMediaFull("",true,"rel='vm-additional-images'");
        }
?>

This div height is related to the thumbnails height settings in the template configuration panel. In the current state, the height takes various value each time the page is refreshed. Setting the width and height to zero or empty in the configuration panel leads to no image being displayed, dynamic resizing being off.

With dynamic resizing on and a value of 100 we got a good rendering, without issue.

200 leads to some issue. This might have to do with the dynamic resizing. Let’s try to locate this code to check what’s happening there.

The style does not appear in the source code. It appears in the DOM only (chrome and firefox). This tend to show that the inline style is generated by javascript. To try to spot the script causing the issue, we will add <script>debugger</script> after the element to debug. Reload the page and add a breakpoint by following the instruction here:

https://developers.google.com/web/tools/chrome-devtools/javascript/breakpoints

Results: Paused on attribute modification: jquery.min.jsmedia/jui/js/jquery.min.js

Stack frame:

style (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
(anonymous) (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
Q (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
Q (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
css (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
(anonymous) (jquery.matchHeight-min.js:8)
each (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
each (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
r.apply (jquery.matchHeight-min.js:8) t.fn.matchHeight (jquery.matchHeight-min.js:7) (anonymous) (jquery.matchHeight-min.js:11) each (jquery.min.js?105ec84b263645980d07d0cf26df4607:2) r._applyDataApi (jquery.matchHeight-min.js:11) u (jquery.min.js?105ec84b263645980d07d0cf26df4607:2) fireWith (jquery.min.js?105ec84b263645980d07d0cf26df4607:2) ready (jquery.min.js?105ec84b263645980d07d0cf26df4607:2) (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)

jquery.matchHeight is a jQuery library to achieve equal height between div. In modern browsers, there are css solutions that achieve the same results: check Flexbox and CSS Grid.

Horme3 template uses /js/jquery.matchHeight-min.js

So matchHeight library does not handle properly the height of the div with the lazy loading technique I am using at the moment. The script is called in the index.php file of the template. Let’s have a look how the site behave without this script.

jquery.matchHeight catch the data-mh=”media-container” attributes to perform its operation. I tried to remove it from the thumbnail wrapper and add it to the product wrapper but the result is a mess. I will check if I can rewrite the template to support Flexbox instead.

So the issue of height not being set when the src is missing or invalid has also caught the attention of css-tricks which wrote a nice article about the topic. Let’s see if we can implement their options.

Lazy loading draw back: Content Reflow

The solution I will try is to give a svg source at the right size and aspect ratio encoded in base64 (or not: https://css-tricks.com/probably-dont-base64-svg/ )

I got to work out how to configure the viewport attribute in image to get the right aspect ratio.

Also the number of line of the short description is now an issue with the div height. Got to trouble shoot that.

Maybe best would be to restore all changes and reimplement lazy loading and psvg place holder from the ground up to avoid errors introduced when fiddling in the code to find out how to fix the height :)

This is my base64 placeholder (<svg></svg> method did not work):

src=”data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgaWQ9InN2ZzgyMSIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMTQ2Ljg0Mzc1IDkzLjkyNzA4NiIKICAgaGVpZ2h0PSIzNTUiCiAgIHdpZHRoPSI1NTUiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM4MTUiIC8+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhODE4Ij4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICAgIDxkYzp0aXRsZT48L2RjOnRpdGxlPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZwogICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsLTIwMy4wNzI5KSIKICAgICBpZD0ibGF5ZXIxIj4KICAgIDxyZWN0CiAgICAgICByeT0iMC4yNjcyNjk1MiIKICAgICAgIHk9IjIwMi45NDg3NiIKICAgICAgIHg9Ii01LjgxNjU0NjVlLTAwOCIKICAgICAgIGhlaWdodD0iOTQuMDA1MzQxIgogICAgICAgd2lkdGg9IjE0OS42NzA5NCIKICAgICAgIGlkPSJyZWN0NDUwOSIKICAgICAgIHN0eWxlPSJvcGFjaXR5OjAuOTgwMDAwMDQ7ZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDowO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7c3Ryb2tlLW9wYWNpdHk6MSIgLz4KICA8L2c+Cjwvc3ZnPgo=”

Current issue is due to single line description not being correctly adjusted by matchHeight. Otherwise all the rest is working.

Issue was caused by some script being disabled in virtuemart configuration panel. Virtuemart CSS, jquery and Fancybox were disabled.

The place holder should have a height matching the height of the highest thumbnails. Working with normalized thumbnail picture (in terms of dimension) would make things way easier and cleaner.

Overriding templates of admin component takes place in administrator/templates/html/com_virtuemart/helpers/mediahandler.php

Check which admin theme you are using.

Log in the admin dashboard and check the page source, search for

/administrator/templates/your template name/css/template.css?105ec84b263645980d07d0cf26df4607

So at that point all issues have been solved. We need to check why plain SVG failed to load correctly. Specially even with encoding an inkscape file saved as plain svg in base64, we have a file size that have nearly double compared with our current page size. Off course we don’t need to load pictures that are not in the view port which is saving bandwidth and we even gained a smoother experience as the element are already placed properly on the page thanks to the svg place holder which is inline which means no loading time and no recalculation.

That seems to be a win. We need to ensure that the file that needs changes can be overriden properly and also browser compatibility to avoid bad experience.

Understanding joomla .htaccess

IndexIgnore *

## No directory listings
<IfModule autoindex>
IndexIgnore *
</IfModule>

The indexIgnore directive prevent files in a directory with index on from being displayed in the directory listings. The star match all files which will prevent any file in the public directory to be displayed in a directory listings even if it directory listings is on ( apache.conf with the indexes option).

Options +FollowSymlinks

# The line 'Options +FollowSymLinks' may cause problems with some server configurations.
# It is required for the use of mod_rewrite, but it may have already been set by your
# server administrator in a way that disallows changing it in this .htaccess file.
# If using it causes your site to produce an error, comment it out (add # to the
# beginning of the line), reload your site in your browser and test your sef urls. If
# they work, then it has been set by your server administrator and you do not need to
# set it here.
#Can be commented out if causes errors, see notes above.
Options +FollowSymlinks
Options -Indexes

The options directive allow to set the directory behavior. Here FollowSymLinks indicate to follow the symbolic links in the directory (in that case this is the public directory as there is no directory specified).

Indexes indicates that if a directory path is entered and there is no index file in this directory, then a directory listing will be displayes. The minus sign preceding the Indexes option indicate that we remove this behavior, (meaning that it will return a 404?)

This settings might have been set by the host on your server so they might cause issue, in that case you can comment those lines, as stated in the note.

## Mod_rewrite in use.
RewriteEngine On

Turn the rewrite engine on so you can perform redirection from .htaccess according to the conditions you set.

Rewrite rules to block out some common exploits.

## Begin - Rewrite rules to block out some common exploits.
# If you experience problems on your site then comment out the operations listed
# below by adding a # to the beginning of the line.
# This attempts to block the most common type of exploit `attempts` on Joomla!
#
# Block any script trying to base64_encode data within the URL.
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
# Block any script trying to set a PHP GLOBALS variable via URL.
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
# Block any script trying to modify a _REQUEST variable via URL.
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
# Return 403 Forbidden header and show the content of the root home page
RewriteRule .* index.php [F]

As a platform concerned with security, joomla try to deflect some common attack straight from the start.

Let’s have a look at the rewriteCond directives that they use.

Rewrite condition syntax:
RewriteCond TestString CondPattern [flags]

The Teststring is set to match the query string %{QUERY_STRING}

The condition pattern CondPattern is a regular expression.

Base64_encode:

base64_encode is a php function that will encode a string in base 64

Now let’s have a look at the regular expression that follows:

[^(] in the square brakets is the expression to evaluate. The circumflex accent indicates that it is a negative match (match everything NOT in the list), followed by the list of character to NOT match, here a single opening bracket. The star after the bracket indicates to do that any number of time. We have then an escape string followed by an opening bracket to match an opening bracket (otherwise we would just start a marked sub expression). Then [^)]* will match any character that is not a closing bracket any number of time. And we finish with an escaped closing bracket. So our condition tell us to catch any string ” base64_encode ” followed by any suit of characters containing any string between brackets.

https://regexr.com/

Let’s say this evaluate to true, the rewrite rule will then apply and the whole URL including the query strng will be substituted by index.php and it will be displayed instead.

Rewrite rule syntax:

. match any character and * indicates to do that any number of time.

RewriteRule Pattern Substitution [flags]

The [F] flag indicate to return a 403 response (forbidden)

The flag is [OR] which indicates to evaluate the next condition instead (instead of skipping it?) (or next condition)

GLOBALS and _Request

Here it will match the string GLOBALS. The opening bracket indicate that we will match a group. This group will start with an equal sign OR ( or being the pipe symbol ‘|’) an (escaped) square bracket OR a percent signed folowed by any number or upper case alphabetical characters. The numbers within the curly brackets indicates that the preceding rule shall at least 0 but nor more than 2 times. Check the figure below to have an illustrated example.

The following condition does the same but with the string _REQUEST instead of GLOBALS

Catching script injection

And there is one more condition to catch the query string containing a script tag: (<|%3C)([^s]s)+cript.(>|%3E)

%3C is the unicode for less than. So the first group will catch “less than” character. the second group will catch any character preceding a s and the s character one or more time (that is the plus, the star * catch zero or more time). Then it will catch “cript”. The dot “.” match any character and the last group match the closing “greater than” character .

That was a big part! Now the rest should be easy peasy.

So we have a rewrite base that indicates with what string the URL should be prefixed. In our case it is just a slash

RewriteBase /

SEF Section


RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]


Pattern: .* any URL (any suit of any character)

Substitution: – (dash) indicates that no substitution should be performed.

Flag:

E= Causes an environment variable VAR to be set (to the value VAL if provided). The form !VAR causes the environment variable VAR to be unset.

HTTP_AUTHORIZATION: is an environment variable sent in the header of the http request.

%{HTTP:Authorization} obtains the actual value from the header.

Read more about authentication.

RewriteCond %{REQUEST_URI} !^/index\.php

%{REQUEST_URI}  match against the full URL-path in a per-directory RewriteRule

 The NOT character (‘!‘) as a pattern prefix inverse the condition, so it returns true if it does not match the following pattern. Here, the condition returns true if the full URL path is NOT index.php

%{REQUEST_FILENAME} !-f if the requested file is not a file (so if does not exist).

RewriteCond %{REQUEST_FILENAME} !-d or not a directory (so if such directory does not exist)

RewriteRule .* index.php [L]

then rewrite the full path to index.php

[L] is the Last flag which stops the rewriting process.

My 2cents on Google position on Responsive images

Google insists on serving images at the right size using srcset. It has two advantages, the first one is that the page render faster ( as the picture does not have to be resized by the browser to be displayed, if I understand correctly) and that the content in the page does not jump around when the picture is rendered in the browser.

Avoiding content bouncing around when page is rendered

To avoid bad user experience while your page is rendered on the user browser, namely, to avoid your content position being recalculated when the image is loaded, you have two options: one is to set the image width and height in the style and the other, in case of lazyloading is to load a placeholder of the appropriate size in place of the picture. If you are using pictures of a standard size in your page, you justhave to load the picture once to be all set. Both technics are easy to implement.

When setting the width and height of the picture in CSS, you can then serve a larger image to accomodate high dpi screen. Although it leads to a better user experience in terms of quality of image, at the expense of a some milliseconds rendering, google page speeds insight will penalize the practice in its scoring system.

The team at google does not specify why they are so insistant on this. It is a benefit for the user and for the webmaster as the user experience improves but I assume the guys at google might have a special interest in this.

Actually one example they are using to illustrate the consequence of not using properly sized picture is when the user go to click on a link on a page and an image starts rendering, pushing the content down and leading the user to click on whatever was pushed where is mouse or finger landed. The new place being potentially a google ad. On top of a bad user experience, this “practice”, be it volontary or not lead to bad quality trafic to google ad, and I assume this drag the price of google ad clicks down.

Of course google wants to promote quality sites that provide quality user experience, but I don’t see them insisting that much on other practices so I suspect there should be an extra catch here.

As an example of practices leading to bad user experience is the sudden trend there was in web design to display images that slowly, almost imperceptibly zoom in. It can be onload, or on mouse over, etc. I understand the desire to make the page dynamic and modern by displaying sleek animation but the fact is that it gives the sensation of vertigo and unease (think about how you create this sensation in a movie for instance, that’s what you gonna use).

Vertigo stairs from Alfred Hitchcok vertigo movie

Jaws, by Steven Spielberg

So you are using javascript, load time, etc and gives your user the feeling he is sick and needs to take a break from computer right now. But he won’t be clicking inadvertantly on Google ad so that’s ok.

[EDIT: it seems this nonsense trend has already faded to oblivion. ]

Google I/O 2019 take away

The number one take away is to have a light weight website that is mobile friendly.

We have covered some of the very important topic in previous articles about optimization so we will focus here on the surprise features and highlight some point we have skipped previously.

Schema markup for rich results

One of the topic I didn’t cover so far is schema markup for rich results. Google likes it as it allow its search engine to extract the useful information from your page and display that on their search resuls. One can ponder why we should provide content for Google. There is the hope of figuring prominenttly and rank higher but if people get their answer directly from the search page, what economical model do you have left to make money from your website, unless you are paid directly by google. That would be the case for a recipe site.

One thing that has been highlighted in the talk is that schema information might be used with assistive technology, to walk you through a recipe for instance.

Concerning product page, if you are competitive, you might attract customer with your super price or the super brands you have available at your store.

So that’s the two core benefits you can expect from product mark-up. Event mark up might actually be useful to provide straight from the search information about your live event with clear information for google about when the information will be out of date. Leading for a better user experience, unlike the recipe case where you don’t really your recipe to be displayed directly on search page unless you got some compensation from Google for using your content.

For product markup, in sector where negotiation is important, you might not want to have any kind of price displayed publicly to avoid giving any cue to your competitors and allow you larger freedom regarding your pricing. For instance the price might largely vary depending on the quantity of a single item or the total amount of the order due to saving on scale, distance , options, it would be a daunting task to give all available options and integrate all factors influencing the final price and integrate that in a schema markup, plus as seen above it is totally counter productive in terms of conversion as your competition would only have to check your site to propose a more competitive offer.

Needless to say that all schema markup are not made equal and you might consider the benefit of implementing specific schema markup depending on your activity.

Users like large, high definition images

Google aknowledge that people like large, high definition images and will feature those images in their search.

Using proper compression will help reduce the bandwidth necessary to load the image.

Google will start to show in search 3D models directly available in the search results as AR.

There is a talk dedicated to adding 3D to your website:

All those topic needs further investigation, needless to say that product markup is already implemented on my some of my sites.

Light weight website

Time is money and on the web every millisecond of load time count.

Javascript libraries have become too heavy and css frameworks do not use the most advance capabilities of modern css.

Time to refresh your code and crack down on resource call, scripting and rendering.

One of the main reason I used bootsrap was the grid system, simple and efficient. Nowadays css includes grid-column markup:

https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column

https://drafts.csswg.org/css-grid/#propdef-grid-column

The other good reason I had to use Bootstrap framework was the modals. I like their modals, so much I built a webshop with products popping up in modals. The good thing is that you don’t have to go back and forth product pages and category pages and the down side is that category pages code started to grow a tad whith the store products line up.

Avoid webfonts

Replace glyphicon and fontawesome with css sprites use Hover css selector to change color, icon or size on hover and point to another version of the icon etc.

Inline your scripts

Load essential css and js inline for faster rendering

Avoid libraries. Inline your scripts. Defer everything that can be defered.

Optimize image serving

Use fixed size images and use srcset to load properly sized mobile version.

Lazyload images.

One point I need to clarify is the following: properly displaying compressed images on high DPI screen requires to serve an image larger than the size it will be displayed to accomodate the higher density of screen pixel. The questions being: does it impact user experience positively? and Does it impact SEO positively? And John Müller would proabably answers that only the former matters and he would be right in that respect.