Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Adding CSS transitions to outgoing passage in SugarCube

In the end, I suppose that this is a javascript question more than it is a Twine/Twee question. I am trying to add outgoing CSS transitions, a la the Glorious Trainwrecks CSS Transitions plug-in, to the SugarCube header.

SugarCube implements transitions on incoming passages, using the same class name ("transition-in") as the GT plugin, but not on outgoing ("transition-out" in GT). There seem to be two potential ways to add support for the latter:

1) Replace History.prototype.display with a new version that includes that functionality. Unfortunately, my attempts to replace it aren't working. I am trying to do something like this (twee script passage), and am not sure whether I should expect it to (functionally) replace the function in the header or not:
:: CSS Transitions [script]
//function adapted from https://bitbucket.org/tmedwards/sugarcube/src/4771625160fe3ab833a936748a164476b9f7f43e/src/story.js?at=default beginning at line 144
History.prototype.display = function (title, link, render) {
var passage = tale.get(title);
...
// add it to the page
var el = passage.render();
el.style.visibility = "visible";
if (render !== "offscreen") {
var passages = $("#passages");
outgoing = passages.children();
outgoing.addClass("transition-out");
setTimeout(function () {
if (outgoing) outgoing.remove();
}, 1);
...
};
2) Use SugarCube's special PassageReady story passage to intervene before the incoming passage is rendered. This almost works. But somehow doing it this way does not quite control display properly. The only way to actual see the out transition is by including an alert, as I have done in this code:
:: PassageReady [nobr]
/% Runs just before each passage is rendered. %/
<<script>>
var passages = $("#passages");
var outgoing = passages.children();
outgoing.addClass("transition-out");
setTimeout(function () {
if (outgoing) outgoing.remove();
}, 0);
alert("!");
<</script>>
Any ideas what I might be able to do with either of these options to get this working?

Thanks!

Comments

  • #2 cannot work because the elements get removed almost immediately after.  (Which is also the reason why the alert() lets it sort-of function, because it blocks until you click OK.)

    #1 should be fine, as long as you're using it with the same version of the header from which you got it.  Try replacing this section:

    if (render !== "offscreen")
    {
    var passages = document.getElementById("passages");
    removeChildren(passages);
    el.classList.add("transition-in");
    passages.appendChild(el);
    setTimeout(function () { el.classList.remove("transition-in"); }, 1);
    With this:

    if (render !== "offscreen")
    {
    var passages = document.getElementById("passages");
    var outgoing = $(passages).children();
    outgoing.addClass("transition-out");
    setTimeout(function () {
    if (outgoing) { outgoing.remove(); }
    }, 1000); // (in milliseconds) Ideally, you want this to be close to the actual transition period you're using.
    el.classList.add("transition-in");
    passages.appendChild(el);
    setTimeout(function () { el.classList.remove("transition-in"); }, 1);
    You'll also need something like this CSS:

    .passage { transition: none; -webkit-transition: none; } /* clears SugarCube's default transition */
    .transition-in { opacity: 0; }
    .passage:not(.transition-out) { transition: opacity 1000ms ease-in; -webkit-transition: opacity 1000ms ease-in; }
    .transition-out { opacity: 0 !important; }
    I suppose I could add something like this as a configurable to the next release (whenever that will be).
  • #2 doesn't work regardless of what value is used for the timeout; there is something else going on that prevents the animation from appearing--probably it's just that the default History.display code is removing the existing elements before any of the CSS animation can occur. (Sorry for any confusion: the values that I included for timeouts are just the result of my last-ditch experiments to see if I could identify where problems might be--I was using a value of 1000 through most of my testing, since my animations are 1s long.)

    The problem with #1 is that it simply doesn't override SugarCube's default code. I've even tried just replacing all of the code in the function with just an alert, but there's no change in functionality--the games works fine.
  • Erik wrote:
    #2 doesn't work regardless of what value is used for the timeout; there is something else going on that prevents the animation from appearing--probably it's just that the default History.display code is removing the existing elements before any of the CSS animation can occur. (Sorry for any confusion: the values that I included for timeouts are just the result of my last-ditch experiments to see if I could identify where problems might be--I was using a value of 1000 through most of my testing, since my animations are 1s long.)

    (emphasis mine) I already said as much about #2 in my last reply.  Reading failure?

    Erik wrote:
    The problem with #1 is that it simply doesn't override SugarCube's default code. I've even tried just replacing all of the code in the function with just an alert, but there's no change in functionality--the games works fine.

    Then you did it wrong.  I'm not being dismissive, it simply cannot fail to work if done properly.  Perhaps the story wasn't rebuilt afterwards?  You did use the entire method, yes?
  • TheMadExile wrote:

    I already said as much about #2 in my last reply.  Reading failure?

    I'd call it a miscommunication. Your comment on #2 could equally well be read as referring to my own code's immediately calling jQuery's remove() method in response to a 0ms timeout. Which is in fact how I read it.

    TheMadExile wrote:

    Erik wrote:
    The problem with #1 is that it simply doesn't override SugarCube's default code. I've even tried just replacing all of the code in the function with just an alert, but there's no change in functionality--the game works fine.

    Then you did it wrong.  I'm not being dismissive, it simply cannot fail to work if done properly.  Perhaps the story wasn't rebuilt afterwards?  You did use the entire method, yes?

    It looks OK to me and works perfectly well when I paste it into another project, so I'm guessing that there is something that is silently causing the passage not to be read in properly. I'll have to check on that tomorrow.
  • Erik wrote:
    I'd call it a miscommunication. Your comment on #2 could equally well be read as referring to my own code's immediately calling jQuery's remove() method in response to a 0ms timeout. Which is in fact how I read it.

    Ah, yes, I can see that.  Fair enough then.  To clarify though, I was referring to the removeChildren(passages); call in the default code removing the elements almost immediately after PassageReady completes.

    Erik wrote:
    It looks OK to me and works perfectly well when I paste it into another project, so I'm guessing that there is something that is silently causing the passage not to be read in properly. I'll have to check on that tomorrow.

    Well, if you can get it to work in a another project then my off-the-cuff guess would be either a copy-paste snafu or maybe the script tag is misspelled or something like that.

    PS: I have gone ahead and added configurable code for outgoing transitions, so if all else fails I can always push a small release so you can try that.
  • Actually, I had some time, so a new SugarCube release with the configurable outgoing passage transition has been published.

    Example setup:

    :: Outgoing Passage Transition Config [script]
    // # of milliseconds before outgoing elements are removed
    config.passageTransitionOut = 1010; // it's probably best if it's just slightly longer (say 10ms or so) than your outgoing transition delay

    :: Outgoing Passage Transition CSS [stylesheet]
    /* disable SugarCube's default passage transition */
    .passage { transition: none; -webkit-transition: none; }

    /* setup the new passage transition(s) */
    /* this is a simple 3-step fade out */
    .transition-in { opacity: 0; position: absolute; }
    .passage:not(.transition-out) { transition: 0s 1100ms; -webkit-transition: 0s 1100ms; }
    .transition-out { opacity:0; position: absolute; transition: 1000ms steps(3); -webkit-transition: 1000ms steps(3); }
  • Wow, that's great--thanks! I especially like that the length of timeout is configurable via javascript. Combined with code in PassageReady, it ought to be possible to really customize behavior. (My WIP has two different types of transition depending on the type of passage you're going to.)
  • Erik wrote:
    I especially like that the length of timeout is configurable via javascript.

    Yes, I rather like that myself.  It really does need to be configurable somehow, IMO.  If it's a fixed period and you want your transition delay to be noticeably shorter or longer, then you run into situations where you're either stuck waiting for the outgoing elements to disappear or your animation is getting interrupted because the elements were removed too soon.
  • Found the problem that was suppressing my override of the display method: I had two passages with the same name, i.e.:
    :: CSS Transitions [stylesheet]
    ...

    :: CSS Transitions [script]
    ...
    If the Twee compiler were still being developed, I'd suggest that two passages with the same name should result in an error or at least a compiler warning.
  • @TheMadExile: I just learned that you can attach callback functions to CSS transitions. Would it be worthwhile to implement the removal of the outgoing elements from the DOM in such a callback rather than via a timer? (see https://gist.github.com/maccman/4414792 for event names to listen for.) That would eliminate the need to configure the timing value at all.
  • Erik wrote:
    Would it be worthwhile to implement the removal of the outgoing elements from the DOM in such a callback rather than via a timer?

    I'm not sure.  The last time I looked into them, there were still bugs in various desktop browsers related to transition end events (sometimes they don't fire properly, etc).  I also have no clue what support on mobile browsers looks like.
  • Yeah, I was just reading a little deeper and saw that Android in particular doesn't fire reliably...
  • Hmm.  I suppose that could make the current config property accept either an integer (for a timer) or a boolean (to simply enable the event version), so you'd have the option to use either.
  • That sounds good!
  • And, the update to the config property has been released.
  • Great, thanks. There's something in my story that's breaking display when the boolean is used, i.e. set to true (it doesn't break when an integer is supplied): Basically, once you get to one certain passage, the display code stops removing old passages from the DOM (it works fine in the passages before that one). I'll post again if I can figure out what's happening.
  • The only two things that I can think of which could cause that would be if the event handler was failing to attach, which is probably unlikely, or the event has stopped firing, more likely but still odd.  Let me know what you find out or if there's something I can do to help.

    EDIT: Wait, that's actually something you can test.  If the outgoing elements are sticking around, check to see if the div.passage element with the ID out-passage-{passage title slug} has an event handler for the transition end event (whichever one is being used, which is determined and set at startup) bound to it.
  • Got it. There was a wayward :not selector in my css that was unintentionally suppressing the event.
Sign In or Register to comment.