Show and Tell: Creating playable GIFS to document interactive features

Hi all,

I’ve been experimenting with gifs to aid end-users on how to use interactive features on my dashboards and have been getting very good feedback so far. I thought I would share my process with the community.

Here are a couple examples of how this looks in practice when placed in the apps “notes” tab.

First and foremost, credit is due to Thoriq Firdaus, who wrote a blog post about creating playable gifs which happened to be my biggest hurdle in making these gif demos not look overbearing…just imagine a page with several gifs all on loop at the same time. He also created a github repo that explains the recipe. I added some modifications since I felt that a lot of it could be simplified plus I rewrote the script to use ES2015 javascript features and not use jQuery.

So with no further ado, here is how this is done:

Step 1: record a gif and create an image to be the placeholder.

The image will be what the user sees before the play button is clicked. It can be whatever you like, but I find that using the first frame of the GIF makes for a nice transition.

As for GIF creating software, if you are a PC user, my recommendation is ScreenToGif which has a simple UI and a phenomenal editor. If you are a mac user, then first off I envy you, but unfortunately, I don’t have any recommendations as I use PC at work.

Step 2: create html components

The image component’s src attribute will be set to the image you created as the placeholder. The path to the GIF will be stored in the data-alt attribute, which has an unconventional property setting due to its hyphen. Btw, thanks dash team for making this recipe possible by supporting setting data-* and aria-* attributes

The image will then be a child of a figure component like so:

html.Figure(html.Img(
    src='assets/media/{name-of-file}.png',  
    **{'data-alt': 'assets/media/{name-of-file}.gif'}))

Step 3: javascript time

Add the below javascript to your assets folder.

Note that in my case where I have tabs, I need to set a function that fires when the tab is active. I did this by creating an event listener on the window location and adding a callback to set the hash property of a dcc.Location component. If your app never re-renders the dom, then you could remove the listener and immediately call the main entry point.

/**
 * Change the image to .gif when clicked and vice versa
 * 
 * Swap img's `src` and `data-alt` attributes and add a 
 * play class to parent figure to help with the styling.
 */
const toggleGif = (img, figure) => {
  [img.src, img.dataset.alt] = [img.dataset.alt, img.src]
  figure.classList.toggle('play')
}

// Main entry point via event listener on window.location
const locationHashChanged = () => {
  if(location.hash === '#notes') {
    let figures = document.querySelectorAll('figure')
    figures.forEach(e => {
      let img = e.children[0]
      e.addEventListener('click', toggleGif.bind(this, img, e))
    })

  }
}

window.onhashchange = locationHashChanged;

And in my python app.py I have the following callback which simply sets the hash on all tabs. Essentially, all that matters is that my notes tab sets the hash to #notes, but be sure to update the callback and js above accordingly.


# Add dcc.Location in the layout

dcc.Location(id='url', refresh='false'),

...
...
...

# callback to update hash

@app.callback(Output('url', 'hash'),
              [Input('tabs', 'value')])
def update_url_hash(tab):
    return tab

Step 4: Styling

The playable gif recipe I worked off of has an excellent stylesheet with css techniques I never even knew about before so I will just let you guys check out the styles in the repo itself. That said, I do have a couple pointers:

  1. figure:before which sets the play button icon has an absolute position. I found it much easier to set top and left to 50% and then adjusting margin-top and margin-left equally until I got the icon centered.
  2. To use Ionicons, you must add the following as an external stylesheet: https://unpkg.com/ionicons@4.5.5/dist/css/ionicons.min.css

Apendix:

For completeness, I will mention that the recipe I borrowed for the playable gifs had an additional layer of javascript for preloading the gifs. The thought behind this is that there would be a delay when users click play and the gif is still loading. I didn’t fully understand how compatible this was with the way dash renders the DOM, plus I noticed no delay when I removed the feature so I just went ahead without it. If you are curious, below is the non-jQuery version I re-wrote:


// Get all gifs on the page
const getGifs = () => {
  const gifs = [];
  document.querySelectorAll('img').forEach(e => {
    if (e.src.split('.')[1] === 'gif') {
      let data = e.dataset.alt;
      gifs.push(data);
    }
  })
  return gifs;
}


//  This part would get added inside of the main entry point
const gifs = getGifs();
const image = [];  
gifs.forEach((e, i) => {
  image[i] = new Image();
  image[i].src = gif[i]
});

Please let me know what your thoughts are. I’m also still learning javascript so feel encouraged to make any suggestions on how this could be improved or made more elegant.

Cheers!

2 Likes

Update

This approach has been outdated. I have created an all in one dash component that creates a playable gif without any javascript or dcc.Location hacks.

Repo and documentation can be found here: https://github.com/mbkupfer/dash-gif-component

2 Likes

Love this! Thanks for sharing :smiley_cat:

1 Like

This is a simple component, but I still would really like to make some automated tests to make sure this works in many different situations and across all major browsers. I tried working through the pytest example in the boilerplate cookiecutter, but it was a steep learning curve. I do want to revisit this in the next few days though. Do you have any input on how to best set this up?

The easiest way might be to check out our existing tests like in here: https://github.com/plotly/dash/blob/9b24dee076bf26ae51f76e004ca3f0591a559263/tests/integration/test_integration.py#L28-L59.

Many of our tests do screenshot tests with percy.io, so there is very little manual assertions to run - you just set up the app & then take a screenshot of it. Our new testing framework has a simple percy_snapshot method here: https://github.com/plotly/dash/blob/9b24dee076bf26ae51f76e004ca3f0591a559263/tests/integration/test_integration.py#L45