Hacks I've Learned in Developing Interactive Web Pages
Tags: todayilearned, webdev, javascript, css
Published: 2022-04-09 16:43:02

During the development of several interactive web pages, I have learnt a lot. Here are some interesting findings.

## Scroll Bar's Height

Sometimes when we have:

```css
#title {
    position: absolute;
    left: 0;
    bottom: 0;
}
```

to prevent the browser' scroll bar from covering our web page's content;

![Overlapping scrollbars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b0nknohc2sl9vuz00k0z.png) 

we need to subtract the page' size by the scroll bar's width;

```js
const scrollBarWidth = () => {
    let inner = document.createElement('p');
    inner.style.width = "100%";
    inner.style.height = "200px";

    let outer = document.createElement('div');
    outer.style.position = "absolute";
    outer.style.top = "0px";
    outer.style.left = "0px";
    outer.style.visibility = "hidden";
    outer.style.width = "200px";
    outer.style.height = "150px";
    outer.style.overflow = "hidden";
    outer.appendChild(inner);

    document.body.appendChild(outer);
    let w1 = inner.offsetWidth;
    outer.style.overflow = 'scroll';
    let w2 = inner.offsetWidth;
    if (w1 == w2) w2 = outer.clientWidth;
    document.body.removeChild(outer);

    return (w1 - w2);
};
```

So, it will looks like:

![Non-overlapping scrollbars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4zptzrbv4lcalpiuyiaq.png)
 
Credit to http://www.alexandre-gomes.com/?p=115.

## Dynamic Viewport Height

To fit elements to the height of the viewport, we often think to set the height to `100vh`. Unfortunately, this will contribute to a headache when we target mobile devices, because we often see the address bar disappear when scrolling. Until all major browsers support [`dv*` units](https://web.dev/interop-2022/#new-viewport-units), we can create our own scale units. In CSS:

```css
#main-container {
    height: calc(var(--dvh, 1vh) * 100);
}
```

In JavaScript:

```js
let dvh = window.innerHeight * 0.01;
document.documentElement.style.setProperty("--dvh", dvh + "px");
```

This technique will become more of use when dealing with elements with a defined aspect ratio. Consider we have this on iPad:

![Game canvas on iPad](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wgvfte07zol9kupbiuph.png) 

We can set, for example, the font size as:

```css
.title {
    font-size: calc(var(--dvh, 1vh) * 15);
}
```

But on iPhone, we have this instead:

![Game canvas on iPhone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k0151ylbuay9sen9qvmb.png)
 
The font size will not scale as expected. To get rid of this situation, we can do as follow. In CSS:

```css
.title {
    font-size: calc(var(--dch, 1vh) * 15);
}
```

In JavaScript:

```js
// Resize the container to fit the screen
let mainContainer = document.getElementById("main-container");
...

// Create a custom unit
let dch = mainContainer.style.height * 0.01;
document.documentElement.style.setProperty("--dch", dch + "px");
```

So, it will looks like:

![New game canvas on iPhone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c33r0d8cs0l11szzpjb0.png)

Credit to https://css-tricks.com/the-trick-to-viewport-units-on-mobile/.

## Specific Browser Identification

As good web developers, we must ensure that our web pages look equally correct—not necessarily to be precisely the same—on every target browser running on different operating systems. Sadly, we got this:

![Font Rendering on Chrome vs Firefox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ccd2x6faispss93mpdai.png)
 
with CSS as below:

```css
#start-button {
    padding: calc(var(--canvasHeight, 1vh) * 0.75) 0;
    padding-bottom: calc(var(--canvasHeight, 1vh) * 1.0);
    font-size: calc(var(--canvasHeight, 1vh) * 8);
    ...
}
```

Let us take a closer look. In Chrome:

![Font Rendering on Chrome](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mu7tawajx58s5t5epu2w.png)
 
In Firefox:

![Font Rendering on Firefox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zragrdmslsfq6lkeeokf.png) 
 
See? I guess, this is related with [this "bug"](https://bugzilla.mozilla.org/show_bug.cgi?id=1597680).

Someone may have found a better solution, but I would think that I need to apply separate styles with duck-typing technique for Chrome and the others.

For example, we could add:

```css
/*
 * Chrome-only CSS hacks
 **/
@supports (not (-webkit-hyphens: none)) and (not (-moz-appearance: none)) and (list-style-type:"*") {
  #start-button {
      padding-bottom: calc(var(--canvasHeight, 1vh) * 2.0);
  }
}
```

So that it can looks as:

![Hacked Button Style on Chrome vs Firefox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5eoreakqxvipy1p93h0s.png)

Let us zoom in a little:

![Hacked Button Style on Chrome vs Firefox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mu76qiaftlgaedstxyfh.png)

Perfect!

Credit to https://browserstrangeness.github.io/css_hacks.html. Please go to the page to find out more.

In rare cases, we may also want to make our web pages work in other browsers such as Samsung Internet and UC Browser, as they both have accordingly 3% and 1.5% market share in our country. But the previous reference does not include those browsers. In this case, we could have to put this in CSS:

```css
/*
 * UC Browser-only CSS hacks
 **/
html[data-useragent*='UCBrowser'] #start-button {
    padding-bottom: calc(var(--canvasHeight, 1vh) * 2.0);
}
```

In JavaScript:

```js
var root = document.documentElement;
root.setAttribute('data-useragent', navigator.userAgent);
root.setAttribute('data-platform', navigator.platform );
```

Credit to https://stackoverflow.com/a/29938371/8791891.

Not only the way each browser renders the page, but also how they implement things. I had several Safari-specific issues like the `ended` media event not firing and there is audio playback delay. It made me thinking to identify the browser with another duck-typing technique, similar to:

```js
const isSafari = () => {
    return /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && window['safari'].pushNotification));
}
```

Credit to https://stackoverflow.com/a/9851769/8791891.

## Specific Device Identification

In other chances, we may want to make a different implementation based on the user's device type. Let us say that we do not want to enable anti-aliasing to the HTML5 canvas on mobile devices. We can have this code:

```js
const isMobileDevice = () => {
    const agent = navigator.userAgent || navigator.vendor || window.opera;
    return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(agent)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(agent.substr(0,4)));
};
```

Credit to https://stackoverflow.com/a/11381730/8791891

Sometimes, we might find that our web pages do not look the same on desktop and on mobile. Even though they are both Firefox. At this point, we may want to differentiate between the two based on the available media features, combined with the browser detection hack;

```css
/*
 * Firefox on touch input device-only CSS hacks
 **/
@supports selector(:-moz-is-html) {
    @media (any-pointer: coarse) and (not (any-pointer: fine)) {
        #start-button {
            padding-bottom: calc(var(--canvasHeight, 1vh) * 2.0);
        }
    }
}
```

Although, well, this is not accurate.

Credit to https://css-tricks.com/interaction-media-features-and-their-potential-for-incorrect-assumptions/.

See you later!