Triple E

Launching New Site and Tracing a Styling Bug

First off, welcome to the new site! It has been quite a journey over the last year working towards delivering the site. We hope you stick around as we have heaps planned for Triple E in 2021.

Styling Issues in the Modern Web

The ability to use code from third-parties through easy to use package management tools such as npm is one piece that makes modern web development so powerful. Code sharing in this fashion also has a compounding effect where packages you install also rely on third-party packages.

While this sharing is great for building functionality rapidly, it can have unexpected side-effects on the visual or behavioural aspects of your site.

We experienced this kind of side-effect with a package that modified the style of a DOM element which in turn impacted the positioning of another element on the page.

First, Some Context

To understand the issue we faced, we need to provide some basic context first. When reading an article, you may have noticed that if you scroll up an arrow icon will appear in the bottom corner. When you click on the arrow the page will scroll up to the top.

alt text How the arrow should display on the page

Recently, the arrow icon stopped appearing at the bottom of the screen. Instead, it showed up at the bottom of the page and would not follow the scroll.

alt text How the arrow was displaying (not following scroll)

What’s the Deal?

Since the feature was functioning at one point we know that the issue was introduced by a change in the codebase. An effective approach to finding the root cause is to perform a binary search and determine which commit introduced the bug. In fact, git has a built-in feature to assist in performing this type of search called bisect.

Interestingly, we discovered early on that the issue was occurring inconsistently. We found that the arrow had the scroll issue after navigating between pages, but if the page was refreshed or accessed using a direct link the arrow would scroll correctly. Rather than go down the git bisect path, we can investigate this inconsistency further.

Based on this behaviour we can infer that the cause is likely due to the transition between pages. To find the root cause, we can compare the DOM generated after navigating with the DOM generated after accessing the article directly.

Diffing The DOMs

Since we know the bug is transient, we can assume that there is a difference in state between the navigated page and the directly-accessed page. Finding this difference is as simple as saving the two DOM states and comparing them.

alt text Copying DOM structure from dev tools

alt text Once saved we can use vscode to compare them

From the diff above, we can see that there are a few differences between the two DOMs. The majority of differences are from the reordering of HTML elements that have no impact on the semantics of the page. However, we do notice one difference in a div’s inline styling that could impact the behaviour.

<div class="tl-wrapper tl-wrapper--mount tl-wrapper-status--entered" - style="z-index: 1; opacity: 1; transform: translate(0px, 0px);" + style="z-index:1;opacity:1" >

The div has a transform property in its style which is not present in the DOM when directly accessing the page. We can test our hypothesis that this inline style is impacting the arrow by modifying the style in dev tools and testing how it behaves.

alt text The arrow is working again after removing the transform property!

Root Cause Analysis

Now that we know the cause of the bug we need to dig deeper to find why the transform property exists on the element in the first place. We already have two clues on where we can start looking. The first clue is that the transform property is present only after navigating between pages. The other clue is in the name of the div class; specifically tl-wrapper. Searching for tl-wrapper reveals why the first hint should have been an obvious one.

alt text The div is inserted as part of the gatsby-plugin-transition-link module

As it turns out, we use gatsby-plugin-transition-link to animate the transition between pages on the site. The reason why this bug only started occurring recently is because we made a change to the transition animation. To make matters worse, the change was made to circumvent a (different) visual bug.

The plugin itself has two main components; a component where animations can be added called TransitionLink, and, a component with a few pre-built animations to provide an out-of-the-box experience called AniLink.

To avoid building our own animations, we used the AniLink component with the built-in fade animation. We switched from the fade to the paintDrip animation to get around the visual bug we mentioned earlier.

If we take a look at the PaintDrip component which AniLink wraps via a prop, we find the return statement specifying the structure of the component.

return ( <> <TransitionLink exit={{ length: aniLength, trigger: ({ exit, e, node }) => this.createRipple(exit, e, props.hex, props.color, node), }} entry={{ delay: aniDelay, length: aniLength, trigger: ({ entry, node }) => this.slideIn(entry, node, direction), }} {...props} > {props.children} </TransitionLink> </> )

The exit prop on the TransitionLink component sets the animation function which will be called when the navigation is triggered and we are “exiting” the page. For the PaintDrip component the exit function is called createRipple. Similarly, the entry prop sets the animation function (slideIn) that will be called when entering the new page.

slideIn = ({ length }, node, from) => { gsap.from(node, length, { ...this.getDirection(from), ease: 'power1.easeOut', }) }

We can see that the slideIn function calls gsap.from which is a function inside the GSAP animation library. Effectively, slideIn uses gsap to slide the tl-wrapper element onto the screen when entering the page. This is why the tl-wrapper element exists; it wraps the page so that it can be animated, taking all the other elements on the page with it.

alt text Dev tools show the transform property changing state while navigating

How We Should Fix it Versus How We Will

Based on all of the information we have gathered so far on the bug, it seems it may be a deep-lying issue in the gsap library. Since gsap is open source we may be able to fix the issue and open a pull-request to get the fixed merged upstream. Unfortunately, gsap’s GitHub page shows that the project is not very contributor-friendly 😞. Even if it was, there is a significant overhead in learning the ins and outs of an unfamiliar project before being able to fix the issue.

Alternatively, we could re-implement the PaintDrip component using a different underlying animation library. This approach still has a significant overhead as it involves learning the API of a new animation library and does not necessarily mitigate the risk of a similar issue occurring.

There is a node module called patch-package which allows the user to modify an installed module and commit the diff (“patch”) to the project. The package runs a postinstall script to apply the patch on machines after installing the modules for the first time.

In our case we could use patch-package to modify the PaintDrip component and remove the slideIn entry transition. However, there is an even simpler approach which would have the exact same effect.

.tl-wrapper { transform: none !important; }

This small css snippet demonstrates how we can nullify the transform property on the tl-wrapper class. By adding !important we can override the transform property and set it to none.

alt text Importing the style in gatsby-browser.js exposes it

By overriding it the transform property no longer has an effect on the layout.

alt text The inline style no longer impacts the arrow display

The Best Way Is Usually Not the Quickest

In an ideal scenario, we would have spent the time to fix the issue in the GSAP animation library. However, given the circumstances surrounding the ability to contribute to the library, and the complexity of animation libraries in general, that wasn’t possible.

More often than not software development does not work in this ideal manner. Typically you need to deliver code in a timely manner and spending time working on a third-party library may not be an effective use of development. Unfortunately the “hacky” approach may be the only viable option given the existing circumstances.

Either way, we managed to fix our mispositioned arrow and learned a bit about side effects with third-party libraries along the way.