IGreatlyDislikeJavascript / bootstrap-tourist

Quick and easy way to build your product tours with Bootstrap Popovers for Bootstrap 3 and 4. Based on Bootstrap-Tour, but with many fixes and features added.
62 stars 36 forks source link

Backdrop transition between steps #23

Closed IGreatlyDislikeJavascript closed 5 years ago

IGreatlyDislikeJavascript commented 5 years ago

A historic UX issue from original Tour with the backdrop overlay still exists in Tourist. For reference, the original discussion is here: https://github.com/sorich87/bootstrap-tour/issues/711#issuecomment-447564310

@ibastevan has asked the following in the Tour repo, I've moved it to here as the discussion is specifically for Tourist:

With regards to this backdrop issue remaining between steps, have you thought about a fade in/out transition effect between steps instead? So, rather than the backdrop disappearing abruptly, it could slowly fade out while the next backdrop is slowly fading in. Could possibly be more subtle and work well?

Thanks for your feedback. There's a general UX/visual challenge with the whole backdrop thing, this might be a good way to solve it.

A fade out, then a fade in would be easy to implement. However I think you're talking about a crossfade, which requires backdrops from both the hiding step and the showing step to be visible at the same time. (Or does it - are we actually keeping the majority of the backdrop visible, and just fading a div in over the previous element, and fading one out over the next step, hmm....)

I need to think about how to implement that because of some limitations in the code from original Tour. Essentially the "hiding" step is completely divorced from the "showing" step as the tour moves between steps, which is why the current abruptness exists. So it's not quite as simple as I'd like!

In the meantime, it's easy to implement a fade out/fade in if you'd like to try that and see if you think it works. I don't want to do this directly into the repo yet as this is a UX / personal preference thing that I think we need opinions on first!. Do this:

1 - In the CSS, add hidden to the tour-backdrop class, i.e.:

display: hidden;

2 - In tourist.js, locate _showOverlayElements function around line 2238. Insert a fade in after line 2274, so something like this:

line 2274: $("body").append($backdrop); line 2275: $backdrop.fadeIn("fast");

3 - Insert the same fade in after line 2312 (which will now be 2313):

line 2313: $(step.backdropContainer).append($backdropBottom); line 2314: $(".tour-backdrop").fadeIn("fast");

4 - locate _hideOverlayElement function around line 2316, and add a fadeOut followed by a remove in the callback. So replace $(".tour-backdrop").remove(); on line 2324 (which will now be line 2326) with something like:

$(".tour-backdrop").fadeOut("fast", function() { $(".tour-backdrop").remove(); });

Ideally we'd need to add a check around the two fadeIn calls to ensure a backdrop isn't still fading out. But this should give you a feel for how it looks...

thenewbeat commented 5 years ago

What about a completely different approach... instead of removing the backdrop and re-adding, you just adjust the size of each backdrop panel. Adding css transitions so that it animates.

I have no idea how this would look in practice, but in my head it looks cool. :)

IGreatlyDislikeJavascript commented 5 years ago

@thenewbeat yes I can kind of imagine what you're thinking of - it's like the backdrop "box" that frames the element on step N slides and grows/shrinks across the screen to end up framing step N+1 element? That would look cool!

Can we work through the logic of this though. Where we're moving from step N element to step N+1 element, the approach is simple - use jquery.animate or similar to shift the backdrop.

However what happens with the animation in the following scenarios:

  1. Step N is an element, step N+1 is an orphan step with no element
  2. Step N is an orphan step with no element, step N+1 is an element (reverse of previous)
  3. Step N+1 uses delayOnElement, and there is an X second pause between moving from N to N+1 because either (a) the element is still hidden or (b) the element doesn't exist in the DOM yet
  4. Step N triggers onElementUnavailable immediately because the element is missing
  5. Step N triggers onElementUnavailable after delayOnElement has timed out

I think the difficult ones from a UX point of view are the delays and timeouts, where you have an unknown period between step N and step N+1 element becoming available/visible. Especially in the delayOnElement cases and number 3 especially because the code may not even know the offset or dimensions of the step N+1 element.

ibastevan commented 5 years ago

@IGreatlyDislikeJavascript Thank you for explaining how to add the fade transition. I have tested it and I think it is a huge improvement as it makes it slighly less abrupt when switching between steps, however we may run into some issues in certain scenarios where the timing of the fade could potentially cause the backdrop to break, especially when you have steps in play that require the delay option.

A cross fade sounds like an interesting idea, in the hope that one fading out the same time as one fading in, would make it look like the backdrop is always there. Although I doubt the results would work the way imagined and could cause lots of unknown issues, such as the previous step container (highlighted element) would most likely be noticeable on the next step.

An ideal scenario would be having something similar to how intro.js have done their backdrop, but i don't know how difficult that would be to do. Instead of 4 backdrop containers, you would have two, a backdrop container and a highlight (helperLayer) container. The CSS is pretty straightforward as it would be a matter of adding a class to the element that contains a high z-index and postion relative.

It's just a matter of calculating the highlight (helperLayer) container in JavaScript that would be the hard part.

I am sure we have all the values though. We would need to get the width, top, height and left styles which values are already in the .backdrop left and right classes.

.tour-highlight "top" would be the current "top" value from ".tour-backdrop.left" .tour-highlight "height" would be the current "height" value from ".tour-backdrop.left" .tour-highlight "left" would be the current "width" value from ".tour-backdrop.left"

.tour-highlight "width" would need some calculation. .tour-backdrop.right "left" value minus .tour-backdrop.right "width" value = .tour-highlight "width"

results <div class="tour-highlight" style="width: ; top: ; height: ; left: ;"></div>

The highlight container would go after the backdrop layer with absolute positioning and lower z-index. Then the backdrop container would be fixed positioning with the same z-index as the element.

This way the display of the backdrop would always remain visible and then can be hidden on steps that dont require it.

IGreatlyDislikeJavascript commented 5 years ago

So if I understand you correctly, this approach means we have one backdrop div that never changes , and one highlight div that "covers" the element? And then the preventInteraction div over all of them.

I really like this, because it solves other problems - the legacy tour approach of removing the backdrop on the step N in a promise, and creating/showing backdrop on step N+1 in a separate promise. Doing it the way you suggest means we move all backdrop manipulation to showStep(), solving the positioning, delay, missing element issues etc. It will also solve half of the iframe issue.

The highlight container would go after the backdrop layer with absolute positioning and lower z-index. Then the backdrop container would be fixed positioning with the same z-index as the element.

Wouldn't they both be absolute? The backdrop div effectively is always the size of the backdrop container, and has a zindex higher than everything else.

The highlight would also go in the backdrop container, but with a z index higher than the backdrop div.

We have to be careful about the zindexes though, because some plugins need extra fixing for animated elements (e.g.: the selectpicker dropdown).

I can look at this when I get a moment, but it would be great if you wanted to implement it as well. All the backdrop code is in _showOverlayElements(), so the first step would be to implement the 2 div approach into that function. Once it's working there, we can refactor the hide and show code so that it's all working in the showStep flow...

ibastevan commented 5 years ago

I havent taken into consideration the preventInteraction div, that actually has all the required values for the highlight element so we oculd probably duplicate that function. All the highlight div really does is cancel out the background of the backdrop.

So the highlight layer can have z-index: 9999998 and the backdrop will be z-index: 9999999 Then the step element will have a class added to it which will contain z-index: 9999999 !important and postion: relative (so the z-index is higher than the hightlight layer). It is important the highlight layer is position absolute and higher z-index (sorry i said lower but was wrong) so that the element appears over, otherwise you wont see the element content. The highlight layer goes after the backdrop, not inside. Could have an override option for the z-indexing? So if it need to be higher we could manually override it per step basis? I will try and test it out if i get the time. Thanks

IGreatlyDislikeJavascript commented 5 years ago

I found this post from the author of intro.js but I don't really understand the explanation. I can help you implement it though, I've commented on your pull so we can work it through together.

https://stackoverflow.com/questions/15576220/how-introjs-achieve-the-highlighted-areas

I don't understand how the zindex change on elements doesn't cause issues. If we start forcing changes on elements, wouldn't that run the risk of third party plugins simply not displaying correctly? Appreciate it works for intro.js, I'm just curious (health warning: I'm not a js coder or web developer!)

ibastevan commented 5 years ago

I tried to follow the way Intro.js have done theirs in terms of css, and i think the z-index shouldn't cause too many issues. At worst someone will need to change the z-index values inside the css to something more suitable to their application. Or just apply some css overrides in their own theme css.

IGreatlyDislikeJavascript commented 5 years ago

Closing this as we're fixing it with the fork and branch

ibastevan commented 5 years ago

Might be worth noting the conversation is continued here - https://github.com/IGreatlyDislikeJavascript/bootstrap-tourist/pull/24