Twine: Custom CSS passage transitions

I've modified my previous Sugarcane passage transition code to allow you to define your own transitions with CSS.

Update: the Javascript that was on this page is now installed in Twine 1.4.

What this does is remove the default JavaScript transition from Sugarcane, and cause the outgoing passage to gain a "transition-out" class, and the incoming passage to instantly gain and lose a "transition-in" class. This enables you to define new passage transitions by styling these 3 classes using CSS.

CSS Examples

In order for there to be a transition, you must specify the transition property for your passage elements. A very basic example is as follows:

.passage {
	transition: 0.25s linear;
	-webkit-transition: 0.25s linear;
}
The transition property causes elements to transition between styles when they gain and lose them, in a specific way. In this case, it's a quarter-second, purely linear transition. (Other options than "linear" are listed here.) The maximum duration time before the departing passage is removed is 1 second.

The current versions of Opera, Firefox and IE support the plain transition property, but you need to also include the -webkit variant in order to work in Chrome and Safari (you can also include -khtml-transition for Konqueror users, few though they may be).

Now that you've defined the transition itself, you can define the styles that the passages can transition from and to.

A very basic example:

.transition-in {
	position:absolute;
	opacity:0;
}
When a new passage arrives, its style will transition from the "transition-in" style to the normal passage style. In this case, it starts invisible ("opacity:0") and fades in to visibility. Note the "position:absolute" - both "transition-in" and "transition-out" should have either position:absolute or position:fixed if you want them to neatly appear above or below each other.
.transition-out {
	position:absolute;
	opacity:0;
}

Putting identical styles for "transition-out" means that departing passages will fade out of visibility as well. With all 3 of these CSS snippets, you've replicated the "dissolve" transition linked above.

A broad number of CSS attributes can be used with this technique. Experiment!

Combining with tag-based CSS

If you use my tag-based passage CSS code, you can tie transitions to passages tagged with a particular tag. For instance, to bind the "dissolve" transition to passages tagged with "t8n-dissolve":

body[data-tags~=t8n-dissolve] .transition-in {
	position:absolute;
	opacity:0;
}
body[data-tags~=t8n-dissolve] .passage {
	transition:1s;
	-webkit-transition: 1s;
}
body[data-tags~=t8n-dissolve] .transition-out {
	position:absolute;
	opacity:0;
}
I recommend prefixing all transition-based tags with something identical, like "t8n" (a numeronym of "transition"). This way, you can then define a default transition to be used only when no "t8n" tags are present:
body:not([data-tags*=t8n]) .transition-in {
	opacity:0;
	transform: translate(0,3rem);
	-webkit-transform: translate(0,3rem);
	position:absolute;
}
body:not([data-tags*=t8n]) .passage {
	transition: 2s;
	-webkit-transition: 2s;
}
body:not([data-tags*=t8n]) .transition-out {
	opacity:0;
	transform: translate(0,-3rem);
	-webkit-transform: translate(0,-3rem);
	position:absolute;
}

(Remember that [data-tags*=t8n] selects elements whose tags contain "t8n", and :not() selects elements that do not fulfill the requirement in the brackets.)

It's important that if you use multiple transitions, the default transition should be guarded using body:not([data-tags*=t8n]), so that its code doesn't interfere with the other transitions' code.

To see an example where the first four passages are tagged "t8n-dissolve" and the rest are not, see here.

Live examples
All examples are "The Sky in the Room" by Porpentine.

Fade in (the default transition)

.transition-in {
	opacity:0;
	position:absolute;
}
.passage:not(.transition-out) {
	transition: 1s;
	-webkit-transition: 1s;
}
.transition-out {
	opacity:0;
	position:absolute;
}

Specifying :not(.transition-out) means that the transition is applied only for the incoming passage - the departing one instantly gets opacity:0. Note that specifying passage.transition-in would not do anything, since it is by removing the transition-in class that the transition is initiated.

Dissolve

.transition-in {
	position:absolute;
	opacity:0;
}
.passage {
	transition: 1s;
	-webkit-transition: 1s;
}
.transition-out {
	position:absolute;
	opacity:0;
}

Retro 8-bit four-step fade in

.transition-in {
	opacity:0;
	position:absolute;
}
.passage:not(.transition-out) {
	transition: 0.8s steps(3);
	-webkit-transition: 0.8s steps(3);
}
.transition-out {
	opacity:0;
	position:absolute;
}

Fade out

.transition-in {
	opacity:0;
	position:absolute;
}
.passage:not(.transition-out) {
	transition: 0s 1s;
	-webkit-transition: 0s 1s;
}
.transition-out {
	transition: 1s;
	-webkit-transition: 1s;
	opacity:0;
	position:absolute;
}

Vertical wipe
Sadly, this currently only works if you give the passage div a fixed width and height.
If you use this, replace all instances of "1200px" and "800px" with dimensions appropriate for your story.

.transition-in {
	clip: rect(0px, 1200px, 0px, 0px) !important;
}
.passage {
	clip: rect(0px, 1200px, 800px, 0px);
	width: 1200px;
	height: 800px;
	position: absolute;
	transition: 1s linear;
	-webkit-transition: 1s linear;
}
.transition-out {
	clip: rect(800px, 1200px, 800px, 0px);
}

Zoom In

.transition-in {
	opacity:0;
	transform: scale(0.8,0.8);
	-webkit-transform: scale(0.8,0.8);
	position:absolute;
}
.passage {
	width: calc(100% - 12em); // Necessary to keep the zoom origin point consistent.
	width: -webkit-calc(100% - 12em);
	transition: 0.5s ease-out;
	-webkit-transition: 0.5s ease-out;
}
.transition-out {
	opacity:0;
	transform: scale(2,2);
	-webkit-transform: scale(2,2);
	position:absolute;
}

Fast scroll up
Ideally you would use "vh" (viewport height) units instead of "rem" (root em) units, but not enough browsers support it yet. ;_;
You can also make it a slow fading scroll if you change the lengths to something short (like 3rem).

.transition-in {
	opacity:0;
	transform: translate(0,-100rem);
	-webkit-transform: translate(0,-100rem);
	position:absolute;
}
.passage {
	transition: 1s;
	-webkit-transition: 1s;
}
.transition-out {
	opacity:0;
	transform: translate(0,100rem);
	-webkit-transform: translate(0,100rem);
	position:absolute;
}

Garage door
This would work well with a story whose passage divs have a fixed width and height, but unlike the vertical wipe it isn't strictly necessary.

.transition-in {
	opacity:0;
	position:absolute;
}
.passage {
	background-color: #000;
	transition: 1s ease-in;
	-webkit-transition: 1s ease-in;
}
.transition-out {
	position:absolute;
	z-index:3;
	transform: translate(0,-200%);
	-webkit-transform: translate(0,-200%);
}

Focus

.transition-in {
	color:transparent;
	text-shadow: #fff 0 0 1em;
	position:absolute;
}
.passage:not(.transition-out) {
	transition: 1s;
	-webkit-transition: 1s;
}
.transition-out {
	opacity:0;
	position:absolute;
}

"Blur Merge"

.transition-in {
	color:transparent;
	text-shadow: #fff -4em 0 1em, #fff 4em 0 1em;
	position:absolute;
}
.passage:not(.transition-out) {
	transition: 1s;
	-webkit-transition: 1s;
}
.transition-out {
	opacity:0;
	position:absolute;
}

Feel free to report any bugs to @webbedspace.

AttachmentSize
tsitr_fadein.html92.69 KB
tsitr_fadeout.html92.74 KB
tsitr_scrollup.html92.82 KB
tsitr_focus.html92.76 KB
tsitr_sillyrotate.html92.92 KB
tsitr_retrofadein.html92.71 KB
tsitr_spin5seconds.html92.98 KB
tsitr_blur_merge.html92.74 KB
tsitr_garagedoor.html92.78 KB
tsitr_zoomin2.html92.92 KB
tsitr_verticalwipe.html92.87 KB
tsitr_t8ntest.html93.84 KB

Comments

transition sandwich

hello glorious trainwreck,

we are trying to use your tag-based 'spin5seconds' feature
in the same twine story as this 'screen shake' macro:
https://gist.github.com/dariusk/4651698

however, the default mods for the transition css disable the
'screen shake'. any quick fix suggestions?

cheers!