Skip to content

Instantly share code, notes, and snippets.

@csilverman
Last active January 19, 2020 21:22
Show Gist options
  • Save csilverman/9868a3fac0586dfa21782023cf43f50a to your computer and use it in GitHub Desktop.
Save csilverman/9868a3fac0586dfa21782023cf43f50a to your computer and use it in GitHub Desktop.
DRY-reveal
// ----
// Sass (v3.4.25)
// Compass (v1.0.3)
// ----
/* Here's the problem this is trying to solve.
Say an element is supposed to fade in after a Javascript event. It needs to be hidden and then
revealed. If JS is off or broken, though, the element will never be revealed. That
could be a problem.
To solve this, you'd set up a CSS animation that automatically reveals the element after a
certain period of time. (You won't know when exactly JS fails, or how long it
might take to load - that's the downside. You'd have to hardcode a delay, and
then hope that JS loads within that time. I think Typekit's wf-loading did something
similar.)
Things are hidden in different ways. An element might be transparent, or it might
have a certain background color, or it might be positioned offscreen. No one
animation satisfies all situations. So you'll have a series of animations whose only
role is to reveal elements that haven't already been revealed by a JS event. These
animations should be tied to a .no-js class.
What you might do is have a mixin to generate the reveal animation: specify the
start state, end state, and property, and you're there. If you have a lot of elements,
though, that's not going to be very DRY - you might generate multiple instances
of the same animation.
The following code creates each animation only once. If it notices that a
particular kind of animation has already been created, it doesn't write new
code - it just @extends the existing animation.
*/
@function str-replace($string, $search, $replace: "") {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
/* The idea is that we don't want to generate the same reveal
*animations over and over again. We can't tell if an
*animation already exists, but we can generate variables
*based on the animation parameters and then check to
*see if those variables exist. If it doesn't, create
*the animation inside a class. If it does, extend the
*existing class.
*
*Idea: add this to a running list containing
*all the reveal animations, similar to how bootstrap lets you
*add or subtract theme colors. */
// ask for feedback on css-tricks forum: is this useful? Am I
// missing something?
// Set up the list
$all-anims: ();
@function sanitize($string) {
$string: inspect($string);
$string: str-replace($string, "#", "");
@return $string;
}
@mixin safe-reveal($property, $start, $finish, $root:'') {
// $root is an optional parameter for specifying a root-level class,
// like "page" or something. This needs to be specified in the mixin
// parameter list, since it'll need to be appended to .no-js.
// Note that this mixin *cannot* be called in an element contained in
// a root-level container. The resulting selector order will be incorrect;
// the root-level container will be a child of the .no-js container.
// So:
// html.page a { @include safe-reveal("background", #000, #fff); } is not good
// it'll result in ".no-js html.page a"
// a { @include safe-reveal("background", #000, #fff, html.page); } is good
// it'll result in " html.page.no-js a"
$start-clean: sanitize($start);
$finish-clean: sanitize($finish);
$anim-name: #{$property}-#{$start-clean}-#{$finish-clean};
@if index($all-anims, $anim-name) {
// Is this animation name already in the list of animations?
// If the animation already exists, don't generated the code
// for a new one; simply extend the existing animation.
@extend %#{$anim-name};
}
@else {
// If the animation isn't in the list, that means it needs
// to be created.
// First, add it to the list. Make sure to specify that
// you're adding it to the global list.
$all-anims: $all-anims $anim-name !global;
// @at-root ensures that the resulting selector
// (in this case, a placeholder one) is created at the root.
// Needs to be tied to .no-js, since we only want this to run if
// whatever JS that was supposed to trigger the normal behavior
// doesn't run.
@at-root #{$root}.no-js {
%#{$anim-name} {
#{$property}: $start;
animation-name: $anim-name;
animation-duration: 1s;
animation-delay: 2s;
animation-fill-mode: forwards;
}
}
// Having created the selector, we now need to apply it
// to the element in which we invoked the mixin.
@extend %#{$anim-name};
// Here's our reveal animation. It's meant to be super simple, since
// all it does is undo the CSS we're using to hide/show an element
// that will then be shown/hidden by Javascript.
@keyframes #{$anim-name} {
0% {
#{$property}: $start;
}
100% {
#{$property}: $finish;
}
}
}
}
.content {
@include safe-reveal("background", #000, #fff);
}
.zx {
@include safe-reveal("opacity", 0, 1);
}
.xbob {
@include safe-reveal("opacity", 0, 1);
}
.sdfxbob {
@include safe-reveal("opacity", 0, 1);
}
.dave {
@include safe-reveal("opacity", 0, 1);
}
/* Here's the problem this is trying to solve.
Say an element is supposed to fade in after a Javascript event. It needs to be hidden and then
revealed. If JS is off or broken, though, the element will never be revealed. That
could be a problem.
To solve this, you'd set up a CSS animation that automatically reveals the element after a
certain period of time. (You won't know when exactly JS fails, or how long it
might take to load - that's the downside. You'd have to hardcode a delay, and
then hope that JS loads within that time. I think Typekit's wf-loading did something
similar.)
Things are hidden in different ways. An element might be transparent, or it might
have a certain background color, or it might be positioned offscreen. No one
animation satisfies all situations. So you'll have a series of animations whose only
role is to reveal elements that haven't already been revealed by a JS event. These
animations should be tied to a .no-js class.
What you might do is have a mixin to generate the reveal animation: specify the
start state, end state, and property, and you're there. If you have a lot of elements,
though, that's not going to be very DRY - you might generate multiple instances
of the same animation.
The following code creates each animation only once. If it notices that a
particular kind of animation has already been created, it doesn't write new
code - it just @extends the existing animation.
*/
/* The idea is that we don't want to generate the same reveal
*animations over and over again. We can't tell if an
*animation already exists, but we can generate variables
*based on the animation parameters and then check to
*see if those variables exist. If it doesn't, create
*the animation inside a class. If it does, extend the
*existing class.
*
*Idea: add this to a running list containing
*all the reveal animations, similar to how bootstrap lets you
*add or subtract theme colors. */
.no-js .content {
background: #000;
animation-name: background-000-fff;
animation-duration: 1s;
animation-delay: 2s;
animation-fill-mode: forwards;
}
@keyframes background-000-fff {
0% {
background: #000;
}
100% {
background: #fff;
}
}
.no-js .zx, .no-js .xbob, .no-js .sdfxbob, .no-js .dave {
opacity: 0;
animation-name: opacity-0-1;
animation-duration: 1s;
animation-delay: 2s;
animation-fill-mode: forwards;
}
@keyframes opacity-0-1 {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment