Forum Discussion

Jonathan_Hill's avatar
Jonathan_Hill
Super Hero
26 days ago
Solved

Using multiple sliders as an alternative to drag-and-drop, with dynamic Z-index control

Hello!

For this week's ELH Challenge (#489), DavidAnderson asked us "to share an interactive example that shows people how to set a table. Go formal, go casual, or mix it up. It is totally your call." 

So, of course, I built a pirate-themed demo inspired by The Goonies....

This is my latest experiment using customised sliders as an accessible alternative to drag-and-drop. As shown below, by replacing the slider thumb with an image, it's possible to build an 'on rails' drag-and-drop that is also accessible from the keyboard:

Being a bit of a perfectionist, I also used Javascript to change the Z-index position of the 'goblet' slider, so it passes in front of the knife and fork when it is moved, despite initially appearing beneath them.  (We're setting a table - everything needs to be in the right place!)

Z-index is a CSS property that defines the order of overlapping HTML elements. Elements with a higher index will be placed on top of elements with a lower index. Changing the z-index number with Javascript means I can move the goblet from its starting position behind the knife and fork, placing it in front of the knife and fork when appropriate.

This also helped to make all of the sliders 'grabbable' within such a small area, where otherwise the larger slider could block out the other two.

This code moves the goblet to the very top:

let controls = document.querySelector("[data-model-id='5xjQeS51pKT']");
controls.style.zIndex = "999" 


This code returns it to its starting position:

let controls = document.querySelector("[data-model-id='5xjQeS51pKT']");
controls.style.zIndex = "4" 


And this IF/ELSE trigger moves the goblet to the back when the slider is at position 0 and 2.5, or to the very front when it is in any other position:

For good measure, I also added a trigger that moves the slider to the very front whenever it is clicked.

For more information on manipulating the z-index number of objects in Storyline, check out JeffBatt-bf4917's brilliant tutorial here.

But hang on a minute

Testing my demo, I found that the sliders for the knife and fork were accessible from the keyboard, but the slider I used for the goblet was not. It appears that changing the z-index of the slider also knocks out the accessibility controls. 

While I could still highlight the slider using the tab key in the usual fashion, I could no longer change its position using the arrow keys. 

It took a bit of lateral thinking, but I found a workaround...

document.querySelector("#acc-5xjQeS51pKT").addEventListener("keydown", function(e) {
    if (e.key === "ArrowLeft") {
        let currentVal = GetPlayer().GetVar("GOBLET");
        if (currentVal > 0) {
            GetPlayer().SetVar("GOBLET", Math.max(0, currentVal - 0.5));
        }
    } else if (e.key === "ArrowRight") {
        let currentVal = GetPlayer().GetVar("GOBLET");
        if (currentVal < 10) {
            GetPlayer().SetVar("GOBLET", Math.min(10, currentVal + 0.5));
        }
    }
})

When the user highlights the goblet with the Tab key, the above code listens for whether they then press the left or right arrow keys.

If they press the left arrow, the code checks the current position of the goblet and moves it half a step to the left - but won't let it go below zero.

If they press the right arrow, it does the same thing but moves it half a step to the right - but won't let it go above ten.

I've basically replaced the accessibility controls that were affected by the z-index changes, all to ensure the goblet appears in front of the cutlery... 😃

Every day's a school day!

I've attached my master file for anyone who'd like to take a closer look at this. My original idea was to dynamically change the z-index position of all three sliders in relation to each other, to ensure they are all consistently 'grabbable', but I just couldn't get this to work smoothly.

But for my money, this technique presents a viable alternative to drag-and-drop that has potential if explored further.

Let me know what you think!

EDIT: I've also now attached v2 of the master file which incorporates Nedim's debugging. The z-index of all three sliders are now dynamically controlled to maintain the perspective throughout the interaction, and the accessibility controls are working without additional code.

  • I absolutely love this interactivity—great job, Jonathan!

    That said, I did notice some inconsistencies with the "z-index" values throughout the activity. For example, when the goblet is moved, it correctly passes in front of the knife and fork. However, once it’s "dropped" or stationary, the fork or knife moves in front of the goblet when either of them is dragged. Upon debugging, it seems that the "z-index" is being reset to its initial value whenever an element is dropped or stops being moved. 


    After analyzing the scene further, I believe the fork and knife should never pass beneath each other when switching positions. In real life, with one hand (or slider in this case), we’d lift and reposition one object at a time without either going underneath the other. But that’s just my perspective—hope you don’t mind these little observations!

    Anyway, since all the actions occur when either slider moves, I tried to come up with a JavaScript solution to address this issue overall and ensure the code executes whenever either slider is moved. This way, the fork and knife never pass in front of the goblet. Additionally, the fork never passes beneath the knife when switching positions with it, and the knife never passes beneath the fork when switching positions with the fork.

    Example:

     

    const goblet = document.querySelector("[data-model-id='5xjQeS51pKT']");
    const fork = document.querySelector("[data-model-id='6CjWcCjhI4H']");
    const knife = document.querySelector("[data-model-id='6jDKIpbtGdv']");
    
    function logZIndexChange(element, newZIndex) {
      let currentZIndex = window.getComputedStyle(element).zIndex;
      console.log(`${element.getAttribute('data-model-id')} - Current z-index: ${currentZIndex}`);
    
      element.style.setProperty("z-index", newZIndex, "important");
    
      let updatedZIndex = window.getComputedStyle(element).zIndex;
      console.log(`${element.getAttribute('data-model-id')} - New z-index: ${updatedZIndex}`);
    }
    
    logZIndexChange(goblet, "300"); // keep it same for all three triggers
    logZIndexChange(fork, "100"); // 100 when the KNIFE moves. 200 when the FORK moves
    logZIndexChange(knife, "200"); // 200 when the KNIFE moves, 100 when the FORK moves
    
    // Of course, these values can be anything, as long as we understand the concept and know what we’re doing here.

     



    Once again, thank you for sharing this with the community. I’ve learned so much from your example!

  • hotneon's avatar
    hotneon
    Community Member

    That's awesome, love the details like how the fork flips when you cross the plate!

  • Jonathan, this is incredibly cool, so creative, and most importantly is a great reminder for me to watch The Goonies. 

    Seriously though, I can totally imagine using this as a fun interactive way to teach a spacial concept that's just a bit more engaging than pure "matching." I'd be super curious to see if other community members have applications for this. Thank you for sharing! 

  • Nedim's avatar
    Nedim
    Community Member

    I absolutely love this interactivity—great job, Jonathan!

    That said, I did notice some inconsistencies with the "z-index" values throughout the activity. For example, when the goblet is moved, it correctly passes in front of the knife and fork. However, once it’s "dropped" or stationary, the fork or knife moves in front of the goblet when either of them is dragged. Upon debugging, it seems that the "z-index" is being reset to its initial value whenever an element is dropped or stops being moved. 


    After analyzing the scene further, I believe the fork and knife should never pass beneath each other when switching positions. In real life, with one hand (or slider in this case), we’d lift and reposition one object at a time without either going underneath the other. But that’s just my perspective—hope you don’t mind these little observations!

    Anyway, since all the actions occur when either slider moves, I tried to come up with a JavaScript solution to address this issue overall and ensure the code executes whenever either slider is moved. This way, the fork and knife never pass in front of the goblet. Additionally, the fork never passes beneath the knife when switching positions with it, and the knife never passes beneath the fork when switching positions with the fork.

    Example:

     

    const goblet = document.querySelector("[data-model-id='5xjQeS51pKT']");
    const fork = document.querySelector("[data-model-id='6CjWcCjhI4H']");
    const knife = document.querySelector("[data-model-id='6jDKIpbtGdv']");
    
    function logZIndexChange(element, newZIndex) {
      let currentZIndex = window.getComputedStyle(element).zIndex;
      console.log(`${element.getAttribute('data-model-id')} - Current z-index: ${currentZIndex}`);
    
      element.style.setProperty("z-index", newZIndex, "important");
    
      let updatedZIndex = window.getComputedStyle(element).zIndex;
      console.log(`${element.getAttribute('data-model-id')} - New z-index: ${updatedZIndex}`);
    }
    
    logZIndexChange(goblet, "300"); // keep it same for all three triggers
    logZIndexChange(fork, "100"); // 100 when the KNIFE moves. 200 when the FORK moves
    logZIndexChange(knife, "200"); // 200 when the KNIFE moves, 100 when the FORK moves
    
    // Of course, these values can be anything, as long as we understand the concept and know what we’re doing here.

     



    Once again, thank you for sharing this with the community. I’ve learned so much from your example!

    • So floored by the level of depth in your reply here—was so impressed with the original post and so impressed by what you added to it! Incredible collab here. Nedim, did you work on this challenge as well? 

      • Nedim's avatar
        Nedim
        Community Member

        Thank you, Noele! Unfortunately, I didn’t work on this challenge, although I wish I could find more spare time to join the ELH, at least once in a while. Whenever I do have extra time, I prefer to spend it here instead, as it allows me to see the bigger picture of what beginners—or anyone—struggles with the most when it comes to Storyline. This way, I also get to improve my own skills. And I really enjoy being part of this community. 

    • Jonathan_Hill's avatar
      Jonathan_Hill
      Super Hero

      Thanks Nedim, that's exactly the effect I was striving for, but couldn't quite figure out how to do it. Really appreciate the assist.

      Can I ask, are the sliders still accessible from the keyboard within your solution?

      • Nedim's avatar
        Nedim
        Community Member

        Yes, it seems that all sliders are accessible when I highlight either one using the Tab key, and then press the left or right arrow keys to change their values. I did not use initial Javascript code to add event listener for the goblet object. 

        I'm trying to upload the updated file, but it's not going through for some reason.

  • Hi Jonathan,

    Fabulous approach to creating an accessible option to drag-and-drop.

    Could this less-imaginative application work:

    Stack the objects vertically on the left.

    To the right of each object have a slider.

    Across the top use labels to create the sense of labeled columns. So the learner would move each slider into the appropriate “column”.

    For example: On the right is a vertical stack of images of a cat, dog, and bird. To the right of each one is a slider extending into the “columns.” So the learner must move the cat slider into the cat “column;” the dog slider into the Dog “column” and so on.