qmd-lab / closeread

https://closeread.netlify.app/
83 stars 1 forks source link

Build sticky sidebar demo from simpler source #2

Closed andrewpbray closed 1 year ago

andrewpbray commented 1 year ago

Build lua filter to recreate the html output of sidebar-base-quarto.qmd using sidebar-cr.qmd, featuring a simpler syntax. Initial plan:

Markdown syntax:

  1. Wrap all content in sidebar layout in div with custom class.
  2. Label plot with id prefixed with cr.

Lua filter:

  1. Move block with id prefixed with cr into the main column.
  2. Move remaining blocks into sidebar.

Working on branch: cr-sidebar

andrewpbray commented 1 year ago

@jimjam-slam if you want to take a look, I think this is working. You can render sidebar-bar-quarto.qmd and sidebar-cr.qmd and they should yield approximately the same HTML file. You can un-comment-out the stuff at the end of sidebar-cr.qmd to see how it looks when the doc mixes normal layout with sticky sidebar layout.

My next step is to allow other objects to be dropped into the body col like images and arbitrary divs. A next step from there could be to figure out how to do a crossfade from one image to the next on scroll trigger.

jimjam-slam commented 1 year ago

@andrewpbray I see! So all of the content of .cr-sidebar gets pulled out, with the histogram chunk with with cr-id: hist-of-mpg going into a third .column with the sticky settings added, and the rest going into a first .column.

I can also see the long margin-bottoms added to each block of content in the first column to get the scrollytelling focus on one element at a time (I would probably consider splitting it between margin-top and margin-bottom, ).

One alternate technique some people use in scrollytelling is to "slow down" the text scroll by playing with... I can't quite remember, but I think it's perspective and Z-axis translation. You do both to effectively cancel out the size reduction but retain a parallax "slower scroll" effect. it's not always appropriate and is probably a stretch goal, but I wanted to mention it!

For the more immediate concern of having scroll trigger updates on content, I think one thing we can't avoid is that you not only need to tag the content you want to update (ie. the plots going on the right), but also the blocks in the regular content that are doing the triggering—those are the things scrollama.js needs to watch for viewport visibility in order to trigger transitions.

So we know that scrollama is initialised with a selector that marks the elements it needs to watch. In my example that's just the .step class, but it can be another kind of selector (eg. based on attribute). We either need to:

  1. explicitly link each trigger to each block (eg. an attribute like scroll-step="2"); or
  2. just mark all the triggers with a class like .step and then we infer their trigger order from order in the DOM.

Either way, we pass scrollama two functions: one for when triggers enter the viewport and one for when they leave. Those functions get a reference to the element and its index, but—unlike in my example, where the trigger is also the thing to be updated, we need to find the corresponding element to transition using a function like document.querySelector, document.getElementById or document.getElementByClass. (The first one would let us locate by attribute too.)

One other thing to explicitly flag here is that we're focusing on scenarios where visibility triggers a complete 0-100% transition—not scenarios where the progress of an element's transition is itself tied to scroll progress (eg. a path being drawn as the user scrolls and pausing mid-way if the user pauses their scroll). Those effects are cool too, and I'd love to accommodate them, but they're probably outside the scope of what we've discussed so far!

andrewpbray commented 1 year ago

I can also see the long margin-bottoms added to each block of content in the first column to get the scrollytelling focus on one element at a time (I would probably consider splitting it between margin-top and margin-bottom, ).

Yep, makes sense. I've made that change, and tweaked the lua filter to append a sidebar-col class to allow narrower targeting of rules like that (since the figure is also in a paragraph).

One alternate technique some people use in scrollytelling is to "slow down" the text scroll by playing with... I can't quite remember, but I think it's perspective and Z-axis translation. You do both to effectively cancel out the size reduction but retain a parallax "slower scroll" effect. it's not always appropriate and is probably a stretch goal, but I wanted to mention it!

Yep, I've seen that and it can be cool! I think we can tack that on to the list of options that can be added once we have a solid architecture down.

We either need to:

  1. explicitly link each trigger to each block (eg. an attribute like scroll-step="2"); or
  2. just mark all the triggers with a class like .step and then we infer their trigger order from order in the DOM.

This seems to me like one of the main design decisions and I don't have a strong feeling one way or the other. The .fragment class in revealjs operates more like the second and seems to work fine. I guess the main difference here is that the element with those attributes won't be the one who's behavior is being targeted, so it could be a bit more difficult to reason about (and to read the source of).

One other thing to explicitly flag here is that we're focusing on scenarios where visibility triggers a complete 0-100% transition—not scenarios where the progress of an element's transition is itself tied to scroll progress (eg. a path being drawn as the user scrolls and pausing mid-way if the user pauses their scroll). Those effects are cool too, and I'd love to accommodate them, but they're probably outside the scope of what we've discussed so far!

I'm a big fan of the progress stuff - gives such a tight level of control to the user. A good thing to aim for.

andrewpbray commented 1 year ago

I've put together a little diagram to help me think through the sidebar layout when we have transitions between figures triggered by elements in the sidebar. You should be able to edit, so feel free to mark up, copy / paste / riff, whatever you like:

https://link.excalidraw.com/l/4AKpAITWs2z/1rJo2nMxBcg

I tried for a sec this morning trying to get this implemented in straight html / css / js in the form of this example here:

https://russellsamora.github.io/scrollama/sticky-side/

I wasn't able to get it working out of the box - it seemed like I was lacking a few css rules so the formatting was a bit off. Have you been able to get this working?

jimjam-slam commented 1 year ago

I'm having a look a the diagram and seeing if I can help with the sticky-side example now!

jimjam-slam commented 1 year ago

I've sketched out the alternative models we're proposing for the source code in the Excalidraw so we can better look at how the Quarto doc user experience would be!

I've also started on adding Scrollama to the doc (see #4).

In 0e1600f, I'm using quarto.doc.add_html_dependency() to inject Scrollama, the intersection-observer polyfill, and our own scrollama initialisation script (which we need to get Scrollama watching the things we want and reacting in the right way). The latter's just a stub for now (note the console message on page startup); we need to decide on our model (attributes/classes/etc.) to actually implement it.

One thing to keep in mind is that Scrollama does need a selector to watch (just for the trigger blocks, not necessarily for the sticky content blocks that we show).

In 06d9900 and 5b8fdd5, I've adjusted the sticky layout to get us ready for implementing the actual transition functionality with Scrollama. Some things I've done here are:

Let me know what what you think! I haven't started with any Scrollama stuff, so we can still make a decision about the user model we want.

jimjam-slam commented 1 year ago

Finally, in 843b9e3, I've added a quick OJS block down the end of our demo to see if Scrollama (which we've imported with vanilla JS, not OJS) is still available in OJS.

It is! This means that if a user wants to make a "custom" scroller in addition to the one we provide, they can. Although I think putting vanilla JS in an {=html} block might be more appropriate than using OJS... but it's nice to know the option is there!

andrewpbray commented 1 year ago

I'm on vacation till Weds (a bit of off-grid before a conference), but I can't wait to take a look at your ideas!

jimjam-slam commented 1 year ago

No stress at all 😊 Have fun!

andrewpbray commented 1 year ago

Woah, great work on this stuff! A few questions...

Moved most of the styling from inline style attributes in the Lua filter to classes defined in SCSS

Great. As I look at the remaining css that's appended in the Lua filter, I wonder: what's the best way to allow the user to change the default width of the sidebar? One option would be to let them set close-read: sidebar-width: "50%" in the document yaml, which would then be passed into those inline styles in the Lua filter. The other option would be to have this sort of thing handed by editing the .scss directly. What do you think?

Switched the sidebar layout to use a flexbox instead of the grid inherited in the rest of the article. This lets us define a column gap in SCSS, which in turn let me... Remove the explicit margin column from the Lua filter, and... Define a .cr-reverse companion class to flip the order of he sidebar and sticky content (I've also added a hook in the Lua script to pass other existing classes from the layout on, so that users can still hook onto that element)

Terrific. That margin col was definitely a kludge. This is a better way to do it!

Added a system for stacking the sticky elements on top of each other (on the Z-axis, not vertically on the page) based on Revealjs's .r-stack class. The idea with this is that with Scrollama, changing from one figure to the next can be as simple as adding some CSS transitions with some classes—no need to fiddle with trying to dynamically update the contents of the "in view element".

Oh, that's huge; I hadn't seen .r-stack before. I was puzzling over how to address this issue and hadn't come up with any clean solutions.

I've changed the margin-bottom: 80vh to margin-block: 40svh;. This means: The margin is split between top and bottom Use of svh means content shouldn't jump around when mobile browsers show or hide UX chrome like address bars; I've tried to target immediate ancestors of the sidebar column only—except when a markdown heading is used, as those add nested

elements. This means that, if you like, you can group a couple of pars together with a div to prevent the margin rules from spacing them out.

That's really clever: both an simple implementation in the scss and about as straight forward a syntax on the front end as could be hoped for (wrapping in a div)

jimjam-slam commented 1 year ago

No worries at all! I've just added some replies to you in the Excalidraw on preferred user model too.

As I look at the remaining css that's appended in the Lua filter, I wonder: what's the best way to allow the user to change the default width of the sidebar? One option would be to let them set close-read: sidebar-width: "50%" in the document yaml, which would then be passed into those inline styles in the Lua filter. The other option would be to have this sort of thing handed by editing the .scss directly. What do you think?

I don't think it's unreasonable for users to want to edit the SCSS themselves (or more likely attach an additional SCSS sheet on the end to override things), but I also think some basic customisation via frontmatter or section attributes is reasonable if we can swing it—or both! If both were possible, you could set document-level default width via frontmatter and override per-section with an attribute.

This would need to be transformed by the Lua filter, though, even if it was done with attributes. Although attr() can access attribute values in CSS styles, it can currently only do so for the content property (eg. injecting text in via the stylesheet)—not for properties like width.

So the Lua filter would need to resolve system default, document default and section attribute widths and apply the appropriate one to the style attribute.

andrewpbray commented 1 year ago

Makes sense to me!

I added a response on Excalidraw as well. I think 1A sounds like the way to go!

Feel free to merge the PR whenever you're ready.