<noscript><div class="alert alert-danger"><p>To use this site, you'll need to enable JavaScript. If you aren't sure how, <a href="https://enable-javascript.com" target="_blank" class="alert-link">follow these instructions</a>.</p></div></noscript>
<a href="#main" onclick="document.getElementById('main').focus()">Skip to main content</a>
<div class="wrapper">
<main role="main" id="main" tabindex="-1">
<h1>Transition types</h1>
<p>Terms: Element A = departing; Element B = arriving; Scrim = Covering that is translucent, typically behind a modal; Curve = an arc that creates a more natural motion path when moving from point 1 to point 2 (probably not MVP, <a href="http://tobiasahlin.com/blog/curved-path-animations-in-css/">possible in CSS but requires an extra element</a>)</p>
<p><em>PS: There are no easing methods, physics, or proper timelines applied currently. Only showing general types of interaction below.</em></p>
<ul>
<li>
<h2>Fade (aka. Dissolve)</h2>
<div class="content">
<div class="anim fade">
<div class="b small"></div>
<div class="scrim"></div>
</div>
<h4>Use for menus & overlays.</h4>
<p>Appears without transitioning another object out.</p>
<p>Options: Scrim, scale, position, animation curve</p>
</div>
</li>
<li>
<h2>Scale (aka. Pop / Explode)</h2>
<div class="content">
<div class="anim scale">
<div class="b"></div>
</div>
<h4>Use for a modal or for a sub-item that needs a larger work area / becoming the new page focus.</h4>
<p>A resizes from original size to a larger size (typically filling the screen).</p>
<p>Options: Scrim, position, animation curve, shadow, cover %</p>
</div>
</li>
<li>
<h2>Crossfade (aka. Cross dissolve)</h2>
<div class="content">
<div class="anim fade">
<div class="a"></div>
<div class="b"></div>
</div>
<h4>Use for navigating to an unrelated page (aka. moving from a work item page to a main navigation page) AND <a href="https://css-tricks.com/introduction-reduced-motion-media-query/">is the reduced-motion page transition (and prob web) default</a>. </h4>
<p>B fades from 0% to 100% directly over the top of A.</p>
</div>
</li>
<li>
<h2>Reveal (aka. Move in)</h2>
<div class="content">
<div class="anim reveal">
<div class="a"></div>
<div class="scrim"></div>
<div class="b"></div>
</div>
<h4>Use for default page to sub-page transition. Use at non-100% cover for Peek panel. Mobile only.</h4>
<p>B slides in ##% and can cover A entirely. B starts at 0% opacity.</p>
<p>Options: Scrim, shadow, distance, cover %</p>
</div>
</li>
<li>
<h2>Slide</h2>
<div class="content">
<div class="anim slide">
<div class="a"></div>
<div class="b"></div>
</div>
<h4>Use for carousels & tab group swipes.</h4>
<p>Both A & B move together 100% of viewable width.</p>
<p>Options: Scrim</p>
</div>
</li>
<li>
<h2>Flow (aka. Grid)</h2>
<div class="content">
<div class="anim flow">
<div class="a"></div>
<div class="b"></div>
</div>
<h4>Used for moving from one completely different app experience to another.</h4>
<p>A & B both scale back, revealing a background of some sort. They slide a direction so B is now centered. Once B is centered, they zoom back in to cover the viewable area.</p>
<p>Options: Animation curve</p>
</div>
</li>
<li>
<h2>Push</h2>
<div class="content">
<div class="anim push">
<div class="a"></div>
<div class="b"></div>
</div>
<p>B slides in 100% to cover A entirely. A is moving ⅓ that diffence at the same time. Use a scrim or shadow to convey depth. Push works great on small areas, but poorly on larger display areas. Warning: SLOW on older machines</p>
<p>Options: Scrim, shadow</p>
</div>
</li>
<li>
<h2>Flip</h2>
<div class="content">
<div class="anim flip">
<div class="a"></div>
<div class="b"></div>
</div>
<p>A & B rotate on the same axis at the same time. A is hidden, B is revealed. Sometimes used for settings on back of a card, but is a little overkill.</p>
<p>Options: Mask to add 3d lighting</p>
</div>
</li>
<li class="cube">
<h2>Cube</h2>
<div class="content">
<div class="anim cube">
<div class="a"></div>
<div class="b"></div>
</div>
<p>A variation on Flip. Makes cube-like animation instead of a card-like one. Non-performant to animate, since it relies on left/top values (it can't rely on translateX due to rotation decreasing its width).</p>
<p>Options: Mask to add 3d lighting</p>
</div>
</li>
<li class="expand-1">
<h2>Single object expand</h2>
<div class="content">
<div class="anim expand-1">
<div class="b"></div>
<div class="a"></div>
</div>
<p>Revealing new objects in a stream</p>
</div>
</li>
<li class="expand-2">
<h2>Accordian expand</h2>
<div class="content">
<div class="anim expand-2">
<div class="a"></div>
<div class="b"></div>
</div>
<h4>Use for accordians.</h4>
<p>Toggling between active sections</p>
</div>
</li>
<li class="stacks">
<h2>Stacks</h2>
<div class="content">
<div class="anim stacks">
<div class="a"></div>
<div class="b"></div>
<div class="c"></div>
</div>
<p>Reduces height. For quick dismiss review. Should reduce stack unless its serving up N+ items. Yeah, I know, animation needs some smoothing out.</p>
</div>
</li>
<!--li>
<h2>Curl</h2>
<div class="content">
<div class="anim">😔</div>
<p>A peels out, revealing B below. Not commonly used, and not easily replicated with CSS alone. Requires SVG clip-paths & JS for element measurements to reproduce. Probably non-performant to animate also.</p>
</div>
</li-->
</ul>
</main>
</div>
<!-- a11y helpers: 1. Prepositions 2. Error log container -->
<div id="sr-helpers" class="sr-only">
<div id="sr-prepositions">
<div id="srp-for">for</div>
<div id="srp-on">on</div>
</div>
<div id="sr-alerts" role="alert" aria-relevant="additions"></div>
</div>
// Vars
$pad: 8px;
$mobileW: 375px;
$ease: cubic-bezier(.4,.6,.1,1);
$time:3s;
$w:200px;
$h:150px;
// Keyframes
@keyframes Fade {
0%,90%,100%{opacity:0}
50%{opacity:1}
}
@keyframes Scale {
0%,90%,100%{transform-origin: center;transform:scale(.33) translateX(25%) translateY(50%)}
50%{transform-origin: center;transform:scale(1)}
}
@keyframes Expand1B {
0%,90%,100%{max-height:0}
50%{max-height:60px}
}
@keyframes Expand2A {
0%,90%,100%{max-height:60px}
50%{max-height:10px}
}
@keyframes Expand2B {
0%,90%,100%{max-height:10px}
50%{max-height:60px}
}
@keyframes StackA {
0%,100%{z-index:10;transform:scale(.8)}
25%{z-index:10;transform:scale(.8) translateX(150%);opacity:0}
26%{z-index:1;transform:scale(.4) translateY(60%) translateX(0%)}
50%{z-index:1;transform:scale(.4) translateY(60%) translateX(0%);opacity:1}
51%{z-index:2}
75%{transform:scale(.6) translateY(20.5%) translateX(0%);z-index:2}
76%{z-index:10}
}
@keyframes StackB {
0%,100%{z-index:2;transform:scale(.6) translateY(20.5%) translateX(0%);opacity:1}
25%{z-index:10;transform:scale(.8);opacity:1}
50%{z-index:10;transform:scale(.8) translateX(-150%);opacity:0}
51%{z-index:1;transform:scale(.4) translateY(60%) translateX(0%);opacity:0}
75%{z-index:1;transform:scale(.4) translateY(60%) translateX(0%);opacity:1}
}
@keyframes StackC {
0%,100%{z-index:1;transform:scale(.4) translateY(60%) translateX(0%);opacity:1}
1%{z-index:2}
25%{transform:scale(.6) translateY(20.5%) translateX(0%);z-index:2}
49%{z-index:10}
50%{z-index:10;transform:scale(.8);opacity:1}
75%{z-index:10;transform:scale(.8) translateX(150%);opacity:0}
76%{z-index:1;transform:scale(.4) translateY(60%) translateX(0%);opacity:0}
}
@keyframes Reveal {
0%,90%,100%{
transform:translateX(30%);
opacity:0}
50%{transform:translateX(0%);
opacity:1}
}
@keyframes PushA {
0%,90%,100%{transform:translateX(0)}
50%{transform:translateX(-33%)}
}
@keyframes PushB {
0%,90%,100%{
transform:translateX(102%)}
50%{
transform:translateX(0%);
box-shadow:-4px 0 4px #000}
}
@keyframes SlideA {
0%,90%,100%{transform:translateX(0)}
50%{transform:translateX(-100%)}
}
@keyframes SlideB {
0%,90%,100%{transform:translateX(100%)}
50%{transform:translateX(00%)}
}
@keyframes FlowA {
0%,90%,100%{transform:scale(1) translateX(0)}
16%,77%{transform:scale(.5) translateX(0)} // Pull back
33%,63%{transform:scale(.5) translateX(-110%)} // Move
50%{transform:scale(1) translateX(-110%)}
}
@keyframes FlowB {
0%,90%,100%{transform:scale(1) translateX(110%)}
16%,77%{transform:scale(.5) translateX(110%)} // Pull back
33%,63%{transform:scale(.5) translateX(0)} // Move
50%{transform:scale(1) translateX(0)}
}
@keyframes FlipA {
0%,90%,100%{transform: rotateY(0deg)}
50%{transform: rotateY(180deg)}
}
@keyframes FlipB {
0%,90%,100%{transform: rotateY(-180deg)}
50%{transform: rotateY(00deg)}
}
@keyframes CubeA {
0%,100%{transform: rotateY(0deg);left:0}
50%{transform: rotateY(-90deg);left:$w*-1}
}
@keyframes CubeB {
0%,100%{transform: rotateY(90deg);left:$w}
50%{transform: rotateY(0deg);left:0}
}
// Base & helpers
:root {
font-family:sans-serif;
line-height:2.5ex;
background: #f3f3f3;
color: #111;
box-sizing: border-box;
cursor: default;
-ms-overflow-style:-ms-autohiding-scrollbar;
}
*,::before,::after {
animation-fill-mode:both;
box-sizing: border-box;
color: inherit;
cursor: inherit;
font: inherit;
hyphens: auto;
line-height: inherit;
scroll-behavior: smooth;
vertical-align: inherit;
word-break: keep-all;
}
::before,::after {text-decoration: inherit}
[unselectable] {user-select: none}
.disabled,:disabled {
cursor: not-allowed;
pointer-events: none;
}
h1,h2,h3,h4,.h1,.h2,.h3,.h4,[aria-level],table caption,fieldset legend{
font-weight:700;
margin-bottom:0;
text-align:left;
}
h1,.h1,[aria-level='h1']{font-size:200%}
h2,.h2,[aria-level='h2'],table caption{font-size:150%}
h3,.h3,[aria-level='h3'],fieldset legend{font-size:117%}
h4,.h4,[aria-level='h4']{font-size:100%}
textarea {resize: vertical}
.clearfix:after {display:block;clear:both}
.hyphenate{
overflow-wrap: break-word;
word-wrap: break-word;
hyphens:auto;
}
.fx{transition: all .17s cubic-bezier(.2,.6,.36,1)}
.no-fx{transition: none}
:-ms-input-placeholder {color: rgba(0,0,0,.6)}
::placeholder {
color: rgba(0,0,0,.6);
opacity:1;
}
html,body,.wrapper{
width:100%;
height:100%;
padding:0;
margin:0;
}
em{font-style:italic}
// A11y
[aria-busy="true"] {cursor: progress}
[aria-controls] {cursor: pointer}
[aria-disabled] {cursor: default}
.sr-only {
position: absolute !important;
clip: rect(1px, 1px, 1px, 1px);
padding:0 !important;
border:0 !important;
height: 1px !important;
width: 1px !important;
overflow: hidden;
}
body:hover .sr-only a,
body:hover .sr-only input,
body:hover .sr-only button {display: none !important}
a[href='#main']{
color:#fff;
display: inline-block;
text-decoration: none;
background: rgba(0,0,0,.8);
line-height: 1;
padding: .5em;
position: absolute;
top:-2em;
z-index: 999;
}
a[href='#main']:focus{top:auto}
// Custom
body{padding:1em}
main{outline:none}
.anim{
border:1px solid #666;
width:$w;
height:$h;
background:linear-gradient(#7ad,#47a);
margin-bottom:$pad;
position:relative;
overflow:hidden;
perspective: 1000px;
transform-style: preserve-3d;
&,*,*:after{
font-weight:700;
font-size:72px;
line-height:$h;
text-align:center;
}
}
.a,.b,.c,.scrim{
background:#f99;
color:#000;
z-index:1;
backface-visibility: hidden;
&.small{
transform:scale(.66);
}
&,&:after{
position:absolute;
width:$w;
height:$h;
content:"A";
}
&:after{
top:0;
left:0;
}
}
.scrim{
background:rgba(#000,.5);
z-index:2;
&:after{content:""}
}
.b{
background:#bfb;
z-index:3;
&:after{content:"B"}
}
.c{
background:#bbf;
z-index:4;
&:after{content:"C"}
}
ul{
list-style:none;
padding-left:0;
display:flex;
flex-wrap:wrap;
}
li{
flex:1 1 auto;
clear:both;
max-width:18em;
padding:$pad*2;
}
h2{height:60px}
a{color:blue}
// Animations
.anim.fade .b,.anim .scrim{animation:Fade $time infinite}
.anim.scale .b{animation:Scale $time infinite}
.anim.reveal .b{
animation:Reveal $time infinite;
box-shadow:-4px 0 4px #000;
}
.anim.push{
.a{animation:PushA $time infinite}
.b{animation:PushB $time infinite}
}
.anim.slide{
.a{animation:SlideA $time infinite}
.b{animation:SlideB $time infinite}
}
.anim.flow{
.a{animation:FlowA $time*2 infinite}
.b{animation:FlowB $time*2 infinite}
}
.anim.flip{
.a{animation:FlipA $time infinite}
.b{animation:FlipB $time infinite}
}
.anim.cube{
.a{
transform-origin: 100% 50%;
animation:CubeA $time infinite}
.b{
transform-origin: 0% 50%;
animation:CubeB $time infinite}
}
.anim.expand-1,.anim.expand-2{
.a,.b{
height:60px;
width:80%;
margin:7px auto;
position:static;
overflow:hidden;
&,&::after{
height:60px !important;
line-height:60px !important;
font-size:24px;
position:static;
overflow:hidden;
}
}
}
.anim.expand-1{
.b{animation:Expand1B $time infinite}
}
.anim.expand-2{
.a{
margin-bottom:0 !important;
animation:Expand2A $time infinite}
.b{
margin-top:0 !important;
animation:Expand2B $time infinite}
}
.anim.stacks{
.a{animation:StackA $time infinite}
.b{animation:StackB $time infinite}
.c{animation:StackC $time infinite}
}
View Compiled
This Pen doesn't use any external CSS resources.