LinkedInAttic / hopscotch

A framework to make it easy for developers to add product tours to their pages.
Apache License 2.0
4.19k stars 661 forks source link

Support for mobile devices and responsive sites #30

Open halloffame opened 11 years ago

halloffame commented 11 years ago

The bubbles should check to see if they are displaying off screen and if they are they should move and/or resize automatically.

halloffame commented 11 years ago

I added a pull request for this. Let me know what you think. https://github.com/linkedin/hopscotch/pull/31

jrawlings commented 11 years ago

The pull request appears to only address bottom or top placed bubbles. What about left or right placed ones that show up off screen?

snipe commented 9 years ago

Hi all - thanks for the great plugin. Has there been any progress made for responsive design?

timlindvall commented 9 years ago

Sorry, not presently. @kate2753, I think we need to come to some sort of conclusion if this is a plugin candidate or core enhancement. Feels like this should be an update to core since all the placement logic is currently the responsibility of the core library... perhaps a broader rewrite of our placement logic to consider if a given bubble placement will end up off screen. Though, then what edge cases might we start crashing into if we start assuming alternate placements and bubble sizes for tour steps?

kamranayub commented 9 years ago

I can partially address this by separating steps from placement and creating multiple placement configurations. However, it's not great because the same element might be in different positions depending on viewport size (line-wrapping) so I would need to either go even more granular on widths or just deal with it looking bad.

All this really needs to do is intelligently say, "If I am placed off-screen, how far do I need to move (x/y) to be on-screen and then readjust the arrow (if no hard-coded offset, or just override) to still point correctly."

It would be ideal to sort of define placement separately and responsively:

var responsiveTour = {
  id: "responsive",
  steps: [
    {
      target: ".target",
      xOffset: "center",
      arrowOffset: "target",
      media: {
        "min-width: 480px": {
        // exact same options as normal step, but cascades
        },
        "min-width: 768px": {
        // exact same options as normal step, but cascades
        }
      }
    }
  ]

Would use window.matchMedia if available or use matchMedia poly.

By the way, target option for arrow would have it try its best to position itself along x-axis of bubble closest to target element.

For this to work nicely, calculating positioning should always happen after calling onShow or if possible, onBeforeShow because I might take action that adjusts the DOM in preparation for a step (right now I do this in onShow).

kamranayub commented 9 years ago

This is what I added after VERTICAL OFFSET conditional in setPosition.

// ADJUST FOR VIEWPORT
      var wd = { width: $(window).outerWidth(), height: $(window).outerHeight() };
      var right = (left + bubbleBoundingWidth),
          bottom = (top + bubbleBoundingHeight);

      if (right > wd.width) {
         left -= (right - wd.width + 20);
         if (arrowOffset !== 'center' && (step.placement === 'bottom' || step.placement === 'top')) {
            arrowEl.style.left = (arrowOffset + (right - wd.width + 20)) + "px";
         }
      }
      if (left < 0) {
         left += (-left + 20);
      }
      if (bottom > wd.height) {
         top -= (bottom - wd.height + 20);
      }

I know I'm missing a couple cases (moving arrow when top changes) but this fixed my issues. I combined it with the approach I mentioned above (though simpler), like this:

var isMobile = $("header.mobile").css("display") === "block";

// step
{
  target: isMobile ? "#toggle-sidebar" : "#sidebar header",
  placement: isMobile ? "bottom" : "right",
  title: "Foo",
  content: "Bar"
}

I could have just as easily done my own matchMedia query but I use bootstrap so some things are visible/hidden depending on screen size and I'd rather just test the display state of an element.

This pretty much resolves it for me. The code I added to bubbles will simply nudge it back into the viewport. If a user wants to handle special cases, they can do it themselves like I did. A more formal way to declare responsive placement would be the next step.

Note: this doesn't make the tour "responsive" when adjusting viewport because I don't need to handle that. The only use case that would make sense for is ensuring orientation change works.

foxylearning commented 7 years ago

I just wanted to note that kamranayub's solution above worked perfectly for me, except I had some issues with the first line and ended up changing it to:

var wd = { width: screen.availWidth, height: screen.availHeight };

After that minor change, my tour bubbles all now stay within the viewport on mobile devices (though I haven't tested it extensively with every possible option or OS yet).