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:
-
figure:before
which sets the play button icon has an absolute position. I found it much easier to settop
andleft
to 50% and then adjustingmargin-top
andmargin-left
equally until I got the icon centered. - 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!