{ "version": "https://jsonfeed.org/version/1.1", "title": "Dan Wilson", "language": "en", "home_page_url": "https://danielcwilson.com/", "feed_url": "https://danielcwilson.com/feed/feed.json", "description": "Personal website of Dan Wilson", "author": { "name": "Dan Wilson", "url": "https://danielcwilson.com/about" }, "items": [{ "id": "https://danielcwilson.com/posts/mathematicss-constants/", "url": "https://danielcwilson.com/posts/mathematicss-constants/", "title": "The New CSS Math: pi and other constants", "content_html": "
\nCSS added many new Math constants to be used inside Math functions (such as calc()
). The new constants are pi
, e
, infinity
, -infinity
, and NaN
. As of February 2024, these CSS constants are available in the latest Edge, Chrome, Safari and Firefox browsers.\n
pi
#When used inside a Math function (calc()
, pow()
, round()
, etc.), pi
equates to the mathematical constant value pi (π, approximately 3.142). Just as we are able to in JavaScript via Math.pi
we can use the constant directly instead of using an approximated value.
width: calc(pi * 3px); /* ~9.42px */
width: calc(pi * 1rad); /* ~3.14rad = 180deg */
line-height: calc(pi * .5); /* ~1.57 */
\nIt’s important to note that none of these mathematical constants can be used outside of a math function. When used without a math function they will be a generic string. That could still result in valid CSS (such as an animation named pi
used with animation-name: pi
), but more than likely it will be invalid if you are trying to use it as a number. If you want it to equate to the number pi, you’ll need it inside a math function.
line-height: pi; /* invalid */
animation-name: pi; /* valid as string, will use keyframes defined as pi */
animation-iteration-count: pi; /* invalid */
animation-iteration-count: calc(pi); /* valid, ~3.14 */
animation-iteration-count: round(pi, 1); /* valid, 3 */
line-height: pow(pi, 2); /* valid, ~9.87 */
\npi
#The benefits of using pi
appear to be more about clarity of intention than specific mathematical precision. When used in combination with pixels, I would expect pi to represent approximately 3.1415927px. Using getComputedStyle()
in the latest versions of Firefox, Safari, and Chrome (February 2024) gave mildly different calculations when used.
Specified calc() | \nFirefox | \nSafari | \nChrome | \n
---|---|---|---|
pi * 1px | \n3.13333px | \n3.140625px | \n3.14062px | \n
pi * 10px | \n31.4167px | \n31.40625px | \n31.4062px | \n
pi * 100px | \n314.167px | \n314.15625px | \n314.156px | \n
pi * 100000px | \n314159px | \n314159.25px | \n314159px | \n
If you are familiar with using radians in defining angles in mathematical contexts, the introduction of the pi
constant brings more clarity to CSS. Previously, I never found myself reaching for radians when specifying angles in CSS, because they are based around pi. One complete rotation is 2 * pi radians
, and previously we had to specify that in CSS as 6.284rad
or a similar approximation. Radians are not always intuitive when you have to specify values in only a decimal number.
The following are all equivalent ways to define halfway around for a rotation.
\nrotate: 180deg;
rotate: .5turn;
rotate: calc(pi * 1rad);
rotate: 3.142rad;
\nWe can also use pi
to simplify calculating lengths of circles and curves. The total length of a circle’s stroke can be calculated in CSS directly since the circumference of a circle is defined as 2 * pi * radius
, so we can use the raw length for a stroke dash animation.
\n See the Pen \n DashOffset animation with CSS pi by Dan Wilson (@danwilson)\n on CodePen.\n
\ninfinity
#There are two usable constants to represent Infinity which are infinity
and its opposite of -infinity
. Both effectively represent the largest possible value, as a positive or negative, respectively.
z-index: calc(infinity);
z-index: pow(infinity, 1);
left: calc(-infinity * 1px);
opacity: round(down, infinity, infinity); /* weird, but valid */
\nAgain, one of the biggest benefits of using infinity
is clarity of intent. If you are trying to say you want the highest possible pixel value to ensure it is pushed off screen, infinity
might be a clearer (and safer) choice than specifying an arbitrary value you assume is the largest reasonable value.
Depending on the browser and the type calculated, infinity
can compute to fairly different values, but still consistently large. For further reading, Will Boyd discusses some interesting uses cases and computed value fun for infinity
.
e
#To represent the mathematical constant for Euler’s number, we now have... e
! This constant is used with logarithms and exponential growth in mathematics, and it equates to approximately 2.71828.
The same rule applies here as for the other constants: it must be used in a function like calc()
.
opacity: calc(e / 3); /* ~.906 */
line-height: pow(e, 2); /* ~7.389; */
font-size: calc(e * 1rem); /* ~2.71828rem */
opacity: e; /* invalid */
\nNaN
#The last numeric constant added to CSS is NaN
, everyone’s favorite not-a-number representation. As with most of these Math additions to CSS, they allow for correlation to the existing JavaScript Math concepts, and NaN
is no different. Spec-wise, it allows a representation for values that do not exist for certain function usage. For example, finding a remainder with a zero (such as rem(3, 0)
) will result in NaN
because dividing by zero is not mathematically possible. Offering this in the spec gives a way to both map to the JavaScript version of NaN
and represent situations where a CSS calculation does not result in a number in a clear cut manner.
In practice, however, CSS has to resolve to some value. If you try to find the computed value of scenarios with NaN
, used either as a direct value or in a calculation that resolves to it, you will find a valid value ultimately applied.
For example for an element with the following specific CSS
\n#element {
opacity: 1;
opacity: calc(NaN);
}
\nIn each of Firefox, Safari, and Chrome, the second opacity declaration gets applied as it is a valid value. In this case, the computed value applied is 0
. Similarly, if the second opacity had been defined as opacity: rem(9, 0)
(which the spec says should resolve to NaN
whenever the second parameter is zero) the computed value is still 0
since rem(9, 0)
and calc(NaN)
are both effectively the same NaN
.
Attempting width: calc(NaN * 1px)
results in a computed value of 0px
. A z-index: calc(NaN)
is also 0
... as is z-index: calc(NaN * infinity)
.
But now you’re just asking for trouble.
\n", "date_published": "2024-02-28T00:00:00Z" },{ "id": "https://danielcwilson.com/posts/mathematicss-powers/", "url": "https://danielcwilson.com/posts/mathematicss-powers/", "title": "The New CSS Math: pow(), sqrt(), and exponential friends", "content_html": "\nCSS added many new Math functions to supplement the old favorites (such as calc()
). They all ultimately represent a numeric value, but the nuance in how they work is not always clear from the start. As of February 2024, these CSS functions are available in the latest Edge, Chrome, Safari and Firefox browsers.\n
pow()
#Powers in math allow us to specify how many times to multiply a number by itself.
\nNow available in CSS, the pow()
function takes two parameters: our initial number and the exponent value to apply to it.
line-height: pow(3, 2); /* 9 */
line-height: pow(3, 3); /* 27 */
line-height: pow(2, 4); /* 16 */
\nThis mirrors the functionality long available in JavaScript via Math.pow()
.
console.log(Math.pow(3, 2)); // 3 * 3 = 9
console.log(Math.pow(3, 3)); // 3 * 3 * 3 = 27
console.log(Math.pow(2, 4)); // 2 * 2 * 2 * 2 = 16
\nUnlike many of the other new CSS Math Functions, pow()
only works with raw numbers. You cannot apply units inside the function, so in order to get a unit you will need to use pow()
in combination with calc()
,
font-size: calc(1rem * pow(2, 2)); /* 1rem * 4 = 4rem */
rotate: calc(5deg * pow(2, 5)); /* 5deg * 32 = 160deg */
\nJust like we have Math.sqrt()
in JavaScript, we now have sqrt()
in CSS to find the square root of a number. It takes one parameter, and like pow()
it works with just numbers (not lengths, percentages, and other types that have units)
line-height: sqrt(4); /* 2 */
line-height: sqrt(9); /* 3 */
line-height: sqrt(12); /* about 3.4641 */
\nIf you want to take a root that is not square (such as cube root, etc.), you will still need to use pow()
. If you use a fraction as your exponent, you get a root (such as 1/3
for a cube root).
line-height: pow(4, .5); /* square root of 4: 2 */
line-height: pow(4, 1/2); /* square root of 4: 2 */
line-height: pow(27, 1/3); /* cube root of 27: 3 */
line-height: pow(2, 3/2); /* cubed and then a square root: about 2.8284 */
\nAs with many of these new math functions, how you will use them in daily work is not always clear.
\nIn the spec, an example is shown to use pow()
for font sizes and a modular scale. With this each heading, for example, can be in relation to the same root size. While use of pow()
is not strictly needed here as values could be calculated and then pasted in, readability is helped for maintenance as the intent is clearer when showing the pow()
.
\n See the Pen \n CSS pow() and Modular Scale by Dan Wilson (@danwilson)\n on CodePen.\n
\nOr you can think a bit outside the box.
\nCombined with @property
and Custom Properties, we can create a linear animation that feels like it has easing. That is, the animation should proceed equally over time, but by using exponents our animation feels like it is starting slow and then speeding up.
We can animate a variable from 1 to 10 with a linear timing function. Then we apply that variable as the exponent of a pow()
for some transform (like a rotation or translation). The animation will be from 1 to 10 evenly but the value of the transform will increase exponentially, speeding up the resulting visual motion.
\n See the Pen \n exponential animation easing by Dan Wilson (@danwilson)\n on CodePen.\n
\nThere are three other functions related to exponents to know about.
\ne
to a power with exp()
#The exp()
function takes one parameter, representing the exponent to apply to the mathematical constant e
. So exp(3)
is equivalent to pow(e, 3)
, and like pow()
it also does not allow units. This behaves similarly to Math.exp()
in JavaScript.
log()
#Relatedly, log()
takes two parameters (the second is optional) and represents logarithms.
In JavaScript Math.log
only takes one parameter, and it represents the natural logarithm of the number passed as the parameter. The base of this logarithm is e
.
The optional second parameter that CSS adds for log()
allows us to change the base from e
to a new number.
opacity: log(2); /* log 2 for base e = .693147 */
opacity: log(2, 10); /* log 2 for base 10 = .30103 */
\nJavaScript does not have a second parameter, so CSS adds the base change as a convenience. The equivalent of CSS log(number, base)
can be achieved in JavaScript as Math.log(number) / Math.log(base)
.
hypot()
#It’s a lot of words, but it is a fun, niche function.
\nIn geometry with a right triangle, you can find the length of the hypotenuse (the side opposite the right angle) by the following process
\n\n See the Pen \n Hypotenuse on Resize by Dan Wilson (@danwilson)\n on CodePen.\n
\nSo with CSS the hypot()
function (and in JavaScript with the Math.hypot()
method), we can pass in an arbitrary amount of parameters. The function will then square all the parameters, add them together, and take the square root.
animation-iteration-count: hypot(2); /* 2 */
animation-iteration-count: hypot(3, 4); /* 5 = sqrt(9 + 16) = sqrt(25) */
line-height: hypot(1, 2, 3, 4); /* ~5.4772 = sqrt(1 + 4 + 9 + 16) = sqrt(30) */
line-height: hypot(-2); /* 2 */
width: hypot(30px, 40px); /* 50px = sqrt(900px + 1600px) = sqrt(2500px) */
\nThis is the one function discussed in this article that does allow units as long as all items are of the same type.
\ntranslate: hypot(3px, 4px); /* 5px = sqrt(9px + 16px) = sqrt(25px) */
translate: hypot(3px, .25rem); /* 5px = sqrt(9px + 16px) = sqrt(25px) */
rotate: hypot(3deg, 4deg); /* 5deg = sqrt(9deg + 16deg) = sqrt(25deg) */
\n",
"date_published": "2024-02-13T00:00:00Z"
},{
"id": "https://danielcwilson.com/posts/mathematicss-rem-mod/",
"url": "https://danielcwilson.com/posts/mathematicss-rem-mod/",
"title": "The New CSS Math: rem() and mod()",
"content_html": "\nCSS added many new Math functions to supplement the old favorites (such as calc()
). They all ultimately represent a numeric value, but the nuance in how they work is not always clear from the start. As of October 2023, these are available in the latest Safari and Firefox. They are also available in Edge/Chrome behind the Experimental Web Platform feature flag.\n
rem()
function #The mathematical concept of a remainder comes from division, representing what remains when a number does not divide evenly into another number. For example, with 9 ÷ 4
, 9
is not a multiple of 4
, so 4
does not divide evenly into 9
. You can add two 4
s to get 8
, but you still have a 1
left over to get to 9
, so 1
is our remainder.
In JavaScript we can do this with an operator: %
:
console.log(9 % 4); // 1
console.log(5 % 4.1); // 0.9
console.log(1003 % 5); // 3
\nIn CSS, we now have access to the rem()
function to calculate a remainder. It takes two parameters, as JavaScript has two numbers to use with the remainder operator %
. In mathematical terms, the first number is the dividend and the second is the divisor.
The following CSS representations would be equivalent to the previous JavaScript examples:
\nline-height: rem(9, 4); /* 1 */
line-height: rem(5, 4.1); /* 0.9 */
line-height: rem(1003 % 5); /* 3 */
\nSince we are in CSS, we also have to consider units. Both parameter values need to be of the same type, such as a length or an angle representation.
\nrotate: round(20deg, 5deg); /* 0deg */
rotate: round(20deg, 7deg); /* 6deg */
rotate: round(20deg, 3deg); /* 2deg */
\nYou can mix units if they are of the same type. For example we can mix deg
and turn
since both of those represent angles.
rotate: round(100deg, .25turn); /* 10deg (100 % 90) */
rotate: round(200deg, .25turn); /* 20deg (200 % 90) */
\n\n See the Pen \n CSS Math functions: rem() (Visual reference) by Dan Wilson (@danwilson)\n on CodePen.\n
\nThe value will always take the sign of the first parameter (dividend). So if the dividend is negative, the result will be negative. The sign of the second parameter (divisor) has no effect on the result.
\nline-height: rem(9, 4); /* 1 */
line-height: rem(-9, 4); /* -1 */
line-height: rem(9, -4); /* 1 */
line-height: rem(-9, -4); /* -1 */
\nmod()
function #Closely related to the concept of remainders is the modulus function. When both the dividend and divisor have the same sign, both functions have an equivalent result.
\nline-height: rem(9, 4); /* 1 */
line-height: mod(9, 4); /* 1 */
line-height: rem(-9, -4); /* -1 */
line-height: mod(-9, -4); /* -1 */
\nHowever, where the rem()
function takes the sign of the dividend, mod()
takes the sign of the divisor.
line-height: mod(9, 4); /* 1 */
line-height: mod(-9, 4); /* 1 */
line-height: mod(9, -4); /* -1 */
line-height: mod(-9, -4); /* -1 */
\nAnd most importantly... you have to think differently with mod()
when you have a mix of signs. Let’s start with an example:
line-height: rem(-9, 4); /* 1 */
line-height: mod(-9, 4); /* -3 */
line-height: rem(9, -4); /* -1 */
line-height: mod(9, -4); /* 3 */
\nIf you take away the signs, for remainders, we typically think how many multiple of the divsor can fit in the dividend. In the case of rem(9, 4)
two multiples of 4
fit into 9
(because 2 * 4 = 8
), and the remainder is 1
(because 9 - 8 = 1
).
For mod()
in the case where there is one negative sign and one positive sign, I think about it like I am looking for the multiple bigger than the dividend. So for mod(9, -4)
, you look at the multiple just past the dividend (4 * 3 = 12
). And as per usual, we then look at the difference, so our answer is 12 - 9 = 3
.
\n See the Pen \n CSS Math functions: rem() and mod() (Visual reference) by Dan Wilson (@danwilson)\n on CodePen.\n
", "date_published": "2023-10-29T00:00:00Z" },{ "id": "https://danielcwilson.com/posts/mathematicss-round/", "url": "https://danielcwilson.com/posts/mathematicss-round/", "title": "The New CSS Math: round()", "content_html": "\nCSS added many new Math functions to supplement the old favorites (such as calc()
). They all ultimately represent a numeric value, but the nuance in how they work is not always clear from the start. As of October 2023, these are available in the latest Safari and Firefox. They are also available in Edge/Chrome behind the Experimental Web Platform feature flag.\n
round()
#In JavaScript we have Math.round()
which takes one numeric parameter, and the result will be the nearest integer. So 1.4
rounds down to 1
because it is closer to 1
than 2
. Similarly, 3.9
rounds up to 4
since that is the nearest integer.
console.log(Math.round(2.2)); // 2
console.log(Math.round(14.82)); // 15
console.log(Math.round(5.5)); // 6
\nWith CSS, round()
behaves similarly by default, except it takes two parameters. The first is the value you want to round, and the second is the precision number.
When you are thinking general math or JavaScript, the precision number would be 1
because we are always looking at integers, and we are expecting our answer to be a multiple of 1
. For CSS to have the straight equivalent of JavaScript’s Math.round()
, we’d say round(3.9, 1)
.
line-height: round(2.2, 1); /* 2 */
line-height: round(14.82, 1); /* 15 */
line-height: round(5.5, 1); /* 6 */
\nBut... why do I need two parameters?
\nSince we are working with a variety of CSS units with various scales, the specification allows us to work in a manner that is most appropriate for our need.
\nKeeping it unitless for a bit longer, think about opacity
. Its reasonable range is from 0
to 1
. Rounding here to an integer would always lead to a zero or one. This may be what you want, but maybe you want it with a different interval like the tenths place, such that your opacity is always one of 11 values: 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1
. CSS rounding allows this, and you can specify it with precision of 0.1
. So instead of counting up by one, you count up by one tenth.
opacity: round(.56, 0.1); /* 0.6 */
opacity: round(.54, 0.1); /* 0.5 */
\nWe follow the same logic when considering units. Perhaps I want to always rotate an object at intervals of 45 degrees. I can set 45deg
as my precision unit to make sure my rotation is always a multiple of 45 degrees.
rotate: round(20deg, 45deg); /* 0deg */
rotate: round(30deg, 45deg); /* 45deg */
rotate: round(80deg, 45deg); /* 90deg */
\nYou can even mix units if they are of the same syntax. Other angle options exist beyond deg
such as turn
and rad
, so we can use any of these in our function.
rotate: round(20deg, .125turn); /* 0deg */
rotate: round(80deg, .125turn); /* 90deg */
\nCSS round()
takes a third (optional) parameter at the start which allows you to specify a rounding strategy. The earlier examples are all using the default value of nearest
. If you want this strategy, you can either leave out the strategy parameter or specify nearest
explicitly.
/* the following two both use the nearest strategy */
rotate: round(22.8deg, 1deg); /* 23deg */
rotate: round(nearest, 22.8deg, 1deg); /* 23deg */
\nIf we instead want to always round up, we add a starting parameter to specify our rounding strategy as up
. This will be similar to JavaScript’s Math.ceil()
.
font-size: round(up, 20.01rem, 1rem); /* 21rem */
rotate: round(up, 83deg, 5deg); /* 85deg */
rotate: round(up, -83deg, 5deg); /* -80deg */
\nSimilary, we can round down by specifying round(down, ...)
, just as JavaScript allows with Math.floor()
.
font-size: round(down, 20.999rem, 1rem); /* 20rem */
rotate: round(down, 83deg, 5deg); /* 80deg */
rotate: round(down, -83deg, 5deg); /* -85deg */
\nFinally, there is the round(to-zero, ...)
strategy. This one behaves similar to down
when working with positive numbers, but behaves as up
for negative numbers. It will select the interval number that is closest to zero.
rotate: round(to-zero, 83deg, 5deg); /* 80deg */
rotate: round(to-zero, -83deg, 5deg); /* -80deg */
rotate: round(down, 83deg, 5deg); /* 80deg */
rotate: round(down, -83deg, 5deg); /* 85deg */
rotate: round(up, -83deg, 5deg); /* 80deg */
\n\n See the Pen \n CSS Math round(): angles (Visual Reference) by Dan Wilson (@danwilson)\n on CodePen.\n
", "date_published": "2023-08-17T00:00:00Z" },{ "id": "https://danielcwilson.com/posts/clip-path-cutouts/", "url": "https://danielcwilson.com/posts/clip-path-cutouts/", "title": "Cutouts with Clip Paths", "content_html": "Clip paths (and more generally, the shape building functions offered by CSS Shapes) provide mechanisms to turn our rectangular elements into other basic shapes.
\nWith a little extra planning, we can also use clip paths to perform cutouts inside a shape, as seen in this example. The entire Speak and Spell device is made within a single div using (a lot of) box shadows and background gradients... and one clip path to cut out space for the handle. This allows the background of the webpage to be seen from within the element.
\n\n See the Pen \n Single Div Speak & Spell (Has Sound) by Dan Wilson (@danwilson)\n on CodePen.\n
\nWith clip-path: polygon()
we can cut an element to any arbitrary polygon. For example, we can turn a rectangular div
into a triangle by specifying three points (vertices) for our polygon.
div {
clip-path: polygon(
50% 0%,
0% 100%,
100% 100%
)
}
\nTo instead cut out a triangle on the inside, we need our clip path to:
\n\n See the Pen \n Clip Path Cutouts by Dan Wilson (@danwilson)\n on CodePen.\n
\n.inside-cutout {
clip-path: polygon(
evenodd,
/*surround element first*/
0% 0%,
0% 100%,
100% 100%,
100% 0%,
0% 0%,
/* cut interior triangle */
50% 5%, /* point 1: near the top center */
5% 95%, /* point 2: near the bottom left corner */
95% 95%, /* point 3: near the bottom right corner */
50% 5% /* connect back to point 1 of triangle */
);
}
\nThis polygon also uses a fill rule to the clip path, by setting the first parameter on the polygon to evenodd
. This setting allows us to make cuts inside other shapes.
We can effectively nesting our shapes multiple times if we keep making successive cuts into our cutout. With evenodd
as our fill rule, we can alternate between clipped and non-clipped areas. The secret is to always start at a consistant point (start a shape, make the shape, close the shape by connecting to the start, and then moving the next point into the inside of the shape), we can create nested clip paths.
By also using CSS Custom Properties, we can create straightforward animations by changing the points. This example additionally uses CSS Trigonometric functions, so by only changing a set of radius custom properties representing each pentagon’s dimensions, we can alternate between a nested collection of pentagons to a fully filled pentagon on hover.
\n\n See the Pen \n Nested Pentagons by Dan Wilson (@danwilson)\n on CodePen.\n
\nWith trigonometric functions in CSS we can also [introduce curves]](/posts/css-shapes-with-trig-functions) beyond basic circles and ellipses. These approaches can be applied on the inside of a shape as well (as in our initial Speak and Spell example).
\nWe can make a cutout smiley face by surrounding our element fully, entering inside to make one eye, connecting to a second eye, connecting to a mouth, and connecting back to our initial entry point. When dealing with multiple shapes the biggest concerns are making sure you close each shape and connect back along the same line you used to move to the new shape.
\n\n See the Pen \n Curved Cutouts with Clip Paths & CSS Trig Functions by Dan Wilson (@danwilson)\n on CodePen.\n
\nWhen dealing with very long polygon definitions, it can be hard to figure out where specifically an error is when things are not lining up as expected. Take it step by step, and isolate it in a tool that helps with reloading on change (like CodePen) to quickly add or remove points and see the changes in real time.
\n", "date_published": "2023-04-03T00:00:00Z" },{ "id": "https://danielcwilson.com/posts/css-shapes-with-trig-functions/", "url": "https://danielcwilson.com/posts/css-shapes-with-trig-functions/", "title": "Improving CSS Shapes with Trigonometric Functions", "content_html": "CSS Trigonometric functions are supported in the latest versions of Safari, Firefox, Edge, and Chrome. We also discuss animation via @property
, which is supported in the latest Safari, Edge, and Chrome (as of this writing).
The CSS Shapes specification enabled a lot to make interesting shapes on the web today, via clip-path
, shape-outside
, and more. With the introduction of CSS Trigonometric functions, we can simplify how we make regular polygons and build even more complex shapes by more easily approximating curves.
Even though what we discuss will apply to more, we will focus this discussion applying trigonometry to the clip-path
property. We will start with an overview of how to work with basic clip paths and a quick discussion on the math side of trigonometry. However, you can always skip to where we combine trigonometric functions and clip paths and dig into the demos.
With clip paths and Basic Shapes functions, we can take our rectangular elements and define a shape where only portions of our element will be visible. There are a few key functions, but circle()
, ellipse()
, and polygon()
are likely the most straightforward ways to clip to basic shapes.
As the names state, we can create rounded clips via circle
and ellipse
, and we are able to clip our element to any polygon shape with polygon()
by passing in a collection of coordinates. Any valid length or percentage can be used to define the radius and center points for circles and ellipses, as well as for coordinates within polygons.
/* circle with a 2rem radius, at center of element */
clip-path: circle(2rem);
/* circle with a 20% radius, centered on the right side, offset slightly at the top*/
clip-path: circle(20% at 100% 1rem);
/* tall ellipse, at center of element */
clip-path: ellipse(20% 50%);
\n/* rectangle */
clip-path: polygon(
0% 20%,
100% 20%,
0% 60%,
100% 60%
);
/* triangle with base aligned to the bottom of element */
clip-path: polygon(
50% 0%,
0% 100%,
100% 100%
);
\n\n See the Pen \n Clip Path Gallery by Dan Wilson (@danwilson)\n on CodePen.\n
\nAs long as you can determine an X,Y
coordinate for each point on your polygon, you can build impressive clip-path
definitions.
While we get a lot with these basic shape tools, there are still limitations to what can reasonably be built with clip paths (and CSS Shapes in general):
\nNow, I will admit I forgot almost everything about trigonometry in recent years, but it might be time to relearn some things with the introduction of CSS Trigonometric functions. Using angles, sine, cosine, and more can help with positioning, animations, and... drawing shapes.
\nFor this next example, I took two attempts to make a regular pentagon (a five-sided polygon where each side is of equal length). For the first pentagon, I didn’t use equations, I just typed in 5 different coordinates repeatedly until I got something that was close visually. Once I felt pretty good about it, I took another try using the some of the new trigonometric functions sin()
and cos()
available in CSS. The second pentagon is in fact a regular pentagon.
\n See the Pen \n Clip Path Gallery by Dan Wilson (@danwilson)\n on CodePen.\n
\nBefore we dig into the CSS specifics, let’s focus on the mathematics first.
\nTo make a regular polygon, you can place points equidistantly around a circle and connect them with straight lines. So for a pentagon, we can place five points around a circle with a known radius. Since a circle is 360 degrees around, we can place them at 72 degree intervals (the equation to determine the angle interval is n/360
where n
is the number of sides).
But... we are dealing with coordinates and two axes (x
and y
). So how do we place points around a circle based on an angle and a radius?
In a traditional coordinate system, the function cos(<angle>)
helps us determine the x
value of our coordinate when combined with a radius. Similarly, sin(<angle>)
and the radius will be used to determine the y
value.
\n See the Pen \n Visual Reference: Traditional Coordinate System by Dan Wilson (@danwilson)\n on CodePen.\n
\nThe radius of our circle becomes the hypotenuse of a right triangle (don’t you just love reliving all these mathematical terms?) at any point along the circle’s surface.
\nTo determine x
: x = cos(<angle>) * radius
To determine y
: y = sin(<angle>) * radius
A regular pentagon then becomes a collection of five coordinates, where the angles used in the trigonometric functions are 72 degrees apart. For a radius of 1cm:
\n(cos(0deg) * 1cm) (sin(0deg) * 1cm),\n(cos(72deg) * 1cm) (sin(72deg) * 1cm),\n(cos(144deg) * 1cm) (sin(144deg) * 1cm),\n(cos(216deg) * 1cm) (sin(216deg) * 1cm),\n(cos(288deg) * 1cm) (sin(288deg) * 1cm)\n
\nThe hard work is behind us and now we can discuss how to use this in CSS.
\nAs far as CSS and trigonometry are concerned, our previous example is valid CSS once we put it inside a polygon()
and some calc()
s:
clip-path: polygon(
calc(cos(0deg) * 1cm) calc(sin(0deg) * 1cm),
calc(cos(72deg) * 1cm) calc(sin(72deg) * 1cm),
calc(cos(144deg) * 1cm) calc(sin(144deg) * 1cm),
calc(cos(216deg) * 1cm) calc(sin(216deg) * 1cm),
calc(cos(288deg) * 1cm) calc(sin(288deg) * 1cm)
)
\nThose five points give us a similar regular pentagon to our earlier pentagon side-by-side example... almost.
\n\n See the Pen \n Clip Path Gallery (Pentagon) by Dan Wilson (@danwilson)\n on CodePen.\n
\nWe have two primary considerations to think about when moving from the traditional coordinate system to one on our device screens
\n0,0
on our element is the top left corner (regardless of writing mode, etc.)y
values goes positive as you move down the axisSo when dealing with clip paths, if we want our shape to be centered in our element, we need to offset our center point by 50%
. And depending on the orientation we want, it may be useful to multiply some y
values by -1
. In this case, our main desire is to get the element centered, so we can modify our definition with:
clip-path: polygon(
calc(50% + cos(0deg) * 1cm) calc(50% + sin(0deg) * 1cm),
calc(50% + cos(72deg) * 1cm) calc(50% + sin(72deg) * 1cm),
calc(50% + cos(144deg) * 1cm) calc(50% + sin(144deg) * 1cm),
calc(50% + cos(216deg) * 1cm) calc(50% + sin(216deg) * 1cm),
calc(50% + cos(288deg) * 1cm) calc(50% + sin(288deg) * 1cm)
)
\nLet’s assume for the next few examples (as the CodePen demos do) that our element we are clipping is a square shape. We are still using 1cm
so far, but for our pentagon to maximize inside our square element, we really should be using a radius of 50%
since that will allow us to use as much space as possible inside our element. To be flexible going forward, we can make it straightforward to change our center point and radius by using CSS Custom Properties:
--radius: 50%;
--center-x: 50%;
--center-y: 50%;
clip-path: polygon(
calc(var(--center-x) + cos(0deg) * var(--radius)) calc(var(--center-y) + sin(0deg) * var(--radius)),
calc(var(--center-x) + cos(72deg) * var(--radius)) calc(var(--center-y) + sin(72deg) * var(--radius)),
calc(var(--center-x) + cos(144deg) * var(--radius)) calc(var(--center-y) + sin(144deg) * var(--radius)),
calc(var(--center-x) + cos(216deg) * var(--radius)) calc(var(--center-y) + sin(216deg) * var(--radius)),
calc(var(--center-x) + cos(288deg) * var(--radius)) calc(var(--center-y) + sin(288deg) * var(--radius))
)
\nThis is admittedly a fairly long property value to specify five points for a polygon. But we did not have to do any calculations or figure coordinates out visually, we were able to lean on math and put the equations directly in our CSS. And if we want to make small adjustments like change the center point or radius, we do not have to go recalculate all the coordinates and hardcode them.
\nAnother key benefit to using CSS trigonometric functions is that they can be used with any valid CSS angle, whether that is in degrees, radians, turns, or other options. So we could instead define our pentagon in the turn
unit:
clip-path: polygon(
calc(var(--center-x) + cos(0turn) * var(--radius)) calc(var(--center-y) + sin(0deg) * var(--radius)),
calc(var(--center-x) + cos(.2turn) * var(--radius)) calc(var(--center-y) + sin(.2turn) * var(--radius)),
calc(var(--center-x) + cos(.4turn) * var(--radius)) calc(var(--center-y) + sin(.4turn) * var(--radius)),
calc(var(--center-x) + cos(.6turn) * var(--radius)) calc(var(--center-y) + sin(.6turn) * var(--radius)),
calc(var(--center-x) + cos(.8turn) * var(--radius)) calc(var(--center-y) + sin(.8turn) * var(--radius))
)
\nThe added flexibility of the additional angle units is refreshing, especially after working with the JavaScript trigonometry equivalents such as Math.sin()
which only accept radians for the parameter.
As a bonus, animation also becomes more direct in browsers that support @property
(and thus can enable interpolation on the custom property value, independent from the overall clip-path
value):
\n See the Pen \n Clip Path Gallery (animated) by Dan Wilson (@danwilson)\n on CodePen.\n
\nWith the help of trigonometry (and admittedly longer clip path values), we can approximate curves to make even more interesting possibilities
\nNow that we know we can build regular polygons along a circle with trigonometry, we can use that to make polygons with a large amount of sides to create what ultimately appear to be circles or ellipses. For most elements on a page on a large computer monitor, a 60-sided regular polygon will be more than enough sides to make (an approximation of) a circle. Depending on the size of the element, the straight edges likely will not even be visible, so the curve will appear fairly smooth. If you need to can add more sides to the polygon to make it appear more circular.
\n\n See the Pen \n Approximating a Circle by Dan Wilson (@danwilson)\n on CodePen.\n
\nAnd with this capability, we can start to do more with circles, curves, straight edges, and mixing them all together.
\nWe are no longer limited to one circle. So we can build a shape that has the appearance of a snowperson, with two circles overlapping each other.
\n\n See the Pen \n Building a Snowperson (as clip-path) by Dan Wilson (@danwilson)\n on CodePen.\n
\nWe also can plot out a rotated ellipse (remember that using the ellipse()
function will only produce ellipses that are vertical or horizontal). This takes some advanced calculations, but any time there is an equation, it can translate directly to our CSS property value. A little search on the internet led me to an answer to the equations necessary, which then led to the following CSS pattern for each coordinate:
clip-path: polygon(
calc(var(--center-x) + (cos(0deg) * cos(var(--rotation)) * var(--radius-x)) - (sin(0deg) * sin(var(--rotation)) * var(--radius-y)))
calc(var(--center-y) + (cos(0deg) * sin(var(--rotation)) * var(--radius-x)) + (sin(0deg) * cos(var(--rotation)) * var(--radius-y))),
/* followed by many more coordinates */
)
--rotation: .1turn;
--radius-x: 20%;
--radius: 50%;
--center-x: 50%;
--center-y: 50%;
\nAs discussed before, nothing here is brief. I am even still using JavaScript in this case to generate the clip path as a convenience:
\nfunction generateRotatedEllipse() {
const points = [];
for (let i = 0; i < 360; i = i + 6) {
points.push(`calc(var(--center-x) + (cos(${i}deg) * cos(var(--rotation)) * var(--radius-x)) - (sin(${i}deg) * sin(var(--rotation)) * var(--radius-y)))
calc(var(--center-y) + (cos(${i}deg) * sin(var(--rotation)) * var(--radius-x)) + (sin(${i}deg) * cos(var(--rotation)) * var(--radius-y)))`);
}
const ellipse = document.querySelector(\".rotated-ellipse\");
ellipse.style.clipPath = `polygon(${points.join(\",\")})`;
}
\nBut keeping the math calculation in CSS and combine it with CSS Custom Properties gives us a flexible way to build clip paths and update them in the future... or in an animation (as seen in this ellipse demo if your browser supports @property
):
\n See the Pen \n Spinning Elliptical Reveal (via clip-path and @property) by Dan Wilson (@danwilson)\n on CodePen.\n
\nWe have a lot of ways to work with CSS Shapes, and we have a lot more to explore as CSS continues to increase in capabilities.
\n\n See the Pen \n Clip Path Gallery by Dan Wilson (@danwilson)\n on CodePen.\n
\n", "date_published": "2023-03-28T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2021/11/user-media/", "url": "https://danielcwilson.com/blog/2021/11/user-media/", "title": "Mixing Device Cameras and the Web", "content_html": "During this article there will be several examples that require access to your camera. This API is a web standard, and the demos in this article will only have access to video streams if you opt in and allow access. Camera data is only available/visible to you and nothing is saved by this article or its demos.
\nWe are fairly used to the apps on our mobile devices and computers to have access to cameras and microphones for grabbing a selfie or joining a video call. These same media inputs are accessible to our web applications via the Media Devices API. We will be focusing on video input specifically to explore how this API can be used creatively with other web technology.
\nTo follow along with the demo, please view this article on a device with a camera and allow access.
\nAt its core, use of the camera consists of an API call to access the device camera’s video stream and an HTML element (such as video
or canvas
) to send the stream for viewing.
<video playsinline muted autoplay></video>
\nlet stream;
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: false
});
const vid = document.querySelector('video');
vid.srcObject = stream;
vid.onloadedmetadata = () => {
vid.play();
};
}
\nLet’s break down each step of that example
\ngetUserMedia
method. This is the primary method we deal with when working with video. It also requires https
, so sites on plain http
will also not have access to this API. Feature detection is key with all things Media Devices, as every OS + browser + camera combination will be (at least a little) different.getUserMedia
with an options object that tells specifically what we are requesting. In this case we are telling the browser we want the default video stream that it can find, but we do not want any microphone access.video
element’s sourceAs this is permission based, a user can always reject permission. The user should be given enough information about why video access is being requested before it happens so they can make the best choice for them.
\nThere are straightforward reasons why media device access made it to the web, such as it enables videoconferencing. Access to the microphone also opens up possibilities with Speech Recognition. There are even ways to enable screen sharing through a similar Media Devices API (getDisplayMedia()
).
We can get as creative as we want by not just taking video at face value but thinking about it as an input to be combined with everything else that HTML, CSS, and JavaScript give us. People have combined JavaScript with video to create virtual Theremins and color-based music makers. CSS and Canvas open the doors to manipulating the appearance of the video through filters, blend modes, clip paths, and more.
\nFeature detection is always key with accessing video, as not every device/browser/camera will have the same capabilities. There are many different combinations that drive the feature set of your specific video stream we are requesting, and sometimes we will want our video stream to meet specific criteria to be useful.
\nIn our earlier example, we asked if we have access to any video stream by passing { video: true }
into the getUserMedia()
method. This object we pass in represents our Constraints. With { video: true }
we are telling the system to get us effectively any video stream, so this is the loosest constraint we can provide. There will be a default camera with default settings, and that is what we get.
However, we can start making more specific requests. As the developer, if I’d prefer to load a user-facing (selfie) camera, I can tell the API in my constraints object.
\nnavigator.mediaDevices.getUserMedia({
video: { facingMode: \"user\" }
})
\nAlternatively, I could also prefer a camera on the back side of a device, where I could set { facingMode: "environment" }
. The key is that so far these are all preferences. If I request this back camera, but I am on a laptop that only has a front facing camera, then the API will play nice and still give me a video stream from the best available camera.
Other common constraints that can be available are frameRate
, aspectRatio
, and height
/width
.
When we need to apply more restrictions, we can tell the API that we need specific values, by setting ranges or requesting an exact match.
\nnavigator.mediaDevices.getUserMedia({
video: {
facingMode: { exact: \"environment\" },
aspectRatio: { min: 1, max: 1.7777777778 }
}
})
\nThese example constraints will require a camera that faces away from the user and has a stream that is least a square size and no more than a 16:9 ratio. If there is no camera that can support these requirements, no camera is returned and the getUserMedia
Promise is rejected with a MediaStreamError
.
As makers of the web, we have a responsibility to use these abilities well. We need to be clear and honest up front with how these inputs are used by us and what we build, and we need to empower the user to opt out at any point.
\nOnce a video stream is loaded and playing, we no longer can set our constraints for the video via getUserMedia()
. We need to store the video stream and adjust an individual video track within it.
In our starting example, we have a variable named stream
that stores the stream (and its corresponding video track) resolved by the getUserMedia()
Promise. We can act on this individual track at any point — such as stopping the video stream on a button press.
stopButton.addEventListener('click', e => {
// We requested one video and no audio, so there is only one active track
const track = stream.getTracks()[0];
track.stop();
})
\nThis turns the camera off (as you can see if your device has a light indicator for the camera), but the video element might remain on the last captured screen. To assure the user camera access has stopped, we should also set the video.srcObject = null
and remove the stream from our visible video element.
To see the constraints (as we currently requested them) on a given track we can call track.getConstraints()
and we will see an object that matches the one we passed in to getUserMedia
.
To see what Settings actually were applied (that is, which of our preferences became real), we can check track.getSettings()
.
If we want to have better info around what other Capabilities are available to our active stream, we can see the ranges and possible values via track.getCapabilities()
. However, this is one of the few pieces that is not in every major browser yet as it is not in Firefox as of this writing.
Unknown
\nUnknown
\nUnknown
\nFinally, to apply new constraints we can pass in a new constraints object to track.applyConstraints()
. This is a full overwrite, so if we previously had specified a aspectRatio
and on this new update we only set a frameRate
, the old value for the aspectRatio
will be forgotten and its default value will be in effect.
Our constraints object can even pass in a specific deviceId
if we know how the system refers to a specific camera. Realistically we will not be able to know that in most cases ahead of time, but there is another Media Devices API we can use called enumerateDevices
. Calling this will give us a list of all devices available, with the default ones listed first. This method, however, does not require user permission first, and if you call this method before allowing access via getUserMedia
you will get a subset of available information.
[{
\"deviceId\": \"abc\",
\"kind\": \"videoinput\",
\"label\": \"\", // This will be blank until user has granted camera permission
\"groupId\": \"xyz\"
}]
\nIf called after camera access is allowed, we get a slightly different result where the label is filled in.
\n[{
\"deviceId\": \"abc\",
\"kind\": \"videoinput\",
\"label\": \"Front Camera\",
\"groupId\": \"xyz\"
}]
\nWith this information, it’s possible to have a button or dropdown that allows you to switch between cameras and change video streams by passing in the appropriate deviceId
in as a constraint.
stream = await navigator.mediaDevices.getUserMedia({
video: {deviceId: \"abc\"},
audio: false
});
\nNow that we have been responsible and provided many options for our users to opt out or limit their camera usage, we must acknowledge that browsers also provide a lot of options to the user. Just as browsers have introduced ways to mute tabs playing audio in the background, they also (to varying levels) give users control over their camera and microphone usage. Ways to revoke access for all permissions-based methods (such as cameras and location services) have become prominent. So we always need to account for the fact that a user can remove access at any point.
\nSome browsers let you pause and play an active stream’s track without revoking access, and some even allow you to change the active camera.
\n\nHave fun, be creative, and empower users to be creative and in control.
\nThe following is supported in the latest Safari, Firefox, Edge, and Chrome.
\nWhat happens when you have two simultaneous animations in CSS that modify the same property?
\ndiv {
animation:
x 2000ms infinite alternate ease-in,
y 2000ms infinite alternate ease-out;
}
@keyframes x {
from { transform: translateX(0vw) }
to { transform: translateX(90vw) }
}
@keyframes y {
from { transform: translateY(0vh) }
to { transform: translateY(90vh) }
}
\n\nOnly one transform
value can be set at a given time, so only one of the translations will occur (the last one in the animation
list wins, so for this example it is the vertical animation with translateY()
).
The Web Animations API allows us to mix up this behavior through the introduction of the composite
option.
First, we can make an equivalent animation in the WAAPI as the previous CSS example:
\nelement.animate({
transform: ['translateX(0vw)','translateX(90vw)']
}, {
duration: 2000,
easing: 'ease-in',
iterations: Infinity,
direction: 'alternate'
});
element.animate({
transform: ['translateY(0vh)','translateY(90vh)']
}, {
duration: 2000,
easing: 'ease-in',
iterations: Infinity,
direction: 'alternate'
});
\nThe default behavior remains the same, so this code will do the exact same as the CSS, where only the vertical translation will occur.
\nIt does let us start playing with a new option, however, called the composite
property. Its default value is 'replace'
which is the behavior we have discussed so far, where a value on a property is replaced by a newer animation that comes along and tries to modify the same property.
If we change the second animation to add a composite: 'add'
option, we tell it to add the value of this animation to whatever the current state of that property is.
element.animate({
transform: ['translateY(0vh)','translateY(90vh)']
}, {
duration: 2000,
easing: 'ease-in',
iterations: Infinity,
direction: 'alternate',
composite: 'add'
});
\nNow, instead of replacing the value defined by the first animation, it adds our new value. For transform
which has a syntax that accepts a list of transform functions, adding means appending the new transform functions to the end of the existing list. The filter
property is another list-based property that will allow you to keep appending new values to the list of functions.
\n See the Pen \n Transform with composite add by Dan Wilson (@danwilson)\n on CodePen.\n
\nFor properties that are not lists, add behaves slightly differently, but in line with what you may expect. Properties that accept any number-based valu (such as lengths, percentages, or raw numbers) will add the numbers together. So if you have two animations affecting opacity
, the resulting visual opacity will be the sum of the current values of opacity
in each animation.
\n See the Pen \n Opacity Animations with `composite: 'add'` by Dan Wilson (@danwilson)\n on CodePen.\n
\nThe same goes for moving an item 20px + 30px with margin left (not the most performant way to move an object, but it demonstrates length usage)... if the animations both run at the same time, with the same duration and in the same direction, the end result will be a movement of 50px.
\nCurrently, there is no way to specify this same behavior in CSS, though there have been discussions to add it. That doesn’t mean our CSS animations cannot take advantage of this option today.
\nWith the getAnimations()
method on elements (including the document
) you can fetch all the WAAPI animations, CSS animations, and CSS transitions that are active on the element. More importantly, we can modify the keyframes, timing options, or composite
property on any animation returned. So all of a sudden our animations (even our CSS ones) can be updated at any time (even while they are running).
All that to say... you can use the magic of composite
on CSS animations and transitions by adding a little bit of JavaScript:
const animated = document.getElementById('my-element')
animated.getAnimations().forEach(animation => {
animation.effect.composite = 'add';
});
\n\n See the Pen \n Additive CSS Animations by Dan Wilson (@danwilson)\n on CodePen.\n
\nOnce you have an element, you can call getAnimations()
on it, iterate through the array of animations, and update each one to update the composite
property. If you need to only add it to certain animations in the list, you can check the animation.animationName
property for CSS Animations or use other methods to determine which animation is which.
This can also go along with the animationstart
and other events to make sure you are updating the property at an appropriate time. These events do not contain an Animation
object in the handler, but they do return identifying information such as the animationName
. So we can cross reference this event.animationName
with the animationName
found in the getAnimations()
list for CSS Animations to pinpoint and update any animation we are looking for.
animated.addEventListener('animationstart', e => {
const all = animated.getAnimations();
const myAnimation = all.find(anim => anim.animationName === e.animationName);
myAnimation.effect.composite = 'add';
});
\n",
"date_published": "2020-10-03T11:48:30Z"
},{
"id": "https://danielcwilson.com/blog/2020/05/pseudo-waapi/",
"url": "https://danielcwilson.com/blog/2020/05/pseudo-waapi/",
"title": "Pseudo-elements in the Web Animations API",
"content_html": "As of September 2020, the features discussed are now in the latest versions of Firefox, Safari, Edge, and Chrome.
\nOne of the few pieces that was readily available to CSS but not to the Web Animations API was animating pseudo-elements (such as ::before
and ::after
). However, this is starting to be supported in the latest rollout of Web Animations API functionality.
When you use the Web Animations API, you start by getting a reference to an element where you can apply an animation. This will be the original element by default.
\nconst logo = document.getElementById('logo');
const main = logo.animate({ opacity: [0, 1] }, {
duration: 100
});
\nThe second parameter takes our timing options and a few other options (like an id
and composite
). This is where we can specify that we don’t want this animation applied to the main element, but instead a pseudo-element.
const logo = document.getElementById('logo');
const secondary = logo.animate({ opacity: [0, 1] }, {
duration: 100,
pseudoElement: '::after'
});
\nExpected Result:
\n\nLive Result:
\n\n See the Pen \n WAAPI pseudoElement by Dan Wilson (@danwilson)\n on CodePen.\n
\nBy observation, it appears that Firefox supports ::after
, ::before
, and ::marker
. Others (such as ::first-letter
or ::selection
) are not supported. If the browser supports usage of pseudoElement
, but the specified pseudo-element is not valid or not supported, an error will be thrown and no animation will happen.
If you have a reference to an animation, you can check if it is running on a pseudo-element by checking inside the effect
property. Using the animations from the previous examples:
console.log(main.effect.pseudoElement);
// null
console.log(secondary.effect.pseudoElement);
// \"::after\"
\n",
"date_published": "2020-05-04T11:48:30Z"
},{
"id": "https://danielcwilson.com/blog/2020/04/css-in-the-waapi/",
"url": "https://danielcwilson.com/blog/2020/04/css-in-the-waapi/",
"title": "CSS + the Web Animations API",
"content_html": "As of 2022, everything discussed in this article is supported across the latest versions of Safari, Firefox, Edge, and Chrome.
\nWhile the Web Animations API initially brought a similar mechanism as CSS animations to JavaScript, it also added a few additional features like modifying playback rate and jumping to different points in an animation’s timeline. As the final pieces of the Web Animations Level 1 specification roll out to browsers, it’s not just JavaScript that gets extra features.
\nOne of the newer pieces now available is the ability to get references to all the animations for a specific element or even a full document. A reference to an animation is key to be able to update the animation later or set listeners for events.
\n//returns an array of all active animations contained within the document
document.getAnimations();
//returns an array of all active animations for a specific element
document.getElementById('header').getAnimations()
//returns an array of all active animations for a specific element and its descendants
document.getElementById('header').getAnimations({subtree: true})
\nWhen called on the document, every active animation on your page will be returned.
\nWhen called on an element, by default you will get all the active animations on that specific element. You can also specify an options object parameter with the property subtree
enabled if you want to see all the animations for a given element and every element it contains as a descendant.
The best part is that this new Web Animations API method does not just return animations that were created with the Web Animations API, but also CSS Animations and CSS Transitions. Any API method can then be called on the CSS animations and transitions as though it had been created by the API.
\nEach animation that appears in the getAnimations()
array today will be of type Animation
, CSSAnimation
, or CSSTransition
. The two CSS ones extend the Animation
interface.
This means each type will have the normal Web Animations API methods, but the two CSS ones will have an extra property that are specific to those needs.
\n\n See the Pen \n WAAPI: Check if an Animation is from the WAAPI by Dan Wilson (@danwilson)\n on CodePen.\n
\nCSS Animations will have a property called animationName
that will expose the animation name (the same that appears in the CSS animation-name
and the @keyframes
definition). CSS Transitions will similarly have transitionProperty
to share which property it is responsible for transitioning.
const animations = document.getAnimations();
// Iterate through each animation to inspect
animations.forEach(animation => {
if (animation.animationName) {
// CSS Animation with the specified name
} else if (animation.transitionProperty) {
// CSS Transition on the specified property
} else {
// Web Animations API
}
});
\nThat leaves all the rest as ones from the Web Animations API (though, SVG Animations are mentioned in the specification as also making it in here one day). If you need to know which WAAPI animation is which, you can look at the keyframes or timing options, or specify an id
at creation which is returned back.
Since all these animations share a common interface and have the same underlying engine behind them (one of the main goals of the Web Animations specification), we can now use the API to interact with our CSS animations.
\n\n See the Pen \n HyperCSS by Dan Wilson (@danwilson)\n on CodePen.\n
\nFor browsers that do not support getAnimations()
, a simple button that allows the user to start or pause a CSS animation appears. However, with the ability to grab these animations from the API in supported browsers, a range slider is swapped in that allows the user to vary the playback rate of the CSS animations. This happens because the API has the updatePlaybackRate()
that allows us to speed up or slow down an animation, and our CSS Animations can now leverage this feature (there is a known bug in Webkit, however, so playback rate in Safari does not update as of this writing). We can also jump to (or read) specific points in our animation timeline through the currentTime
read/write property, and we can read or update the specific keyframes or timing options (such as duration, delay, and iterations).
There are also more direct ways to cancel or finish an animation. With CSS animations, you could always update the style.animationName = 'none'
in JavaScript, but now you can also call cancel()
on the animation. CSS animations and transitions have long had solid JavaScript event listeners for animationend
, transitionstart
, and others, so the Web Animations API does not bring much new here. That said, the WAAPI offers comparable callbacks and Promises if those are preferred.
And if you want to get a sneak preview of the next ways that the API will be extending capabilities, check out Firefox Nightly to see additive animation in action.
\n", "date_published": "2020-04-16T03:48:30Z" },{ "id": "https://danielcwilson.com/blog/2020/03/blend-modes-in-spaaaace/", "url": "https://danielcwilson.com/blog/2020/03/blend-modes-in-spaaaace/", "title": "When 255 × 0 does not Equal Zero", "content_html": "First, a caveat: I am not an expert in color management and monitor display profiles. I also only understand the surface-level differences between the default color space of sRGB on the web and other newer models. This conversation will include discussions of both, and I’ll explain what I know and leave room (and links) for others to chime in with better explanations.
\nOkay… now with that out of the way (and I guess… sorry for the early spoiler of what the article is going to address): Let’s talk the mathematics of blend modes!
\nIf you have used Photoshop (or bonus points for fellow Corel-Photo-Paint-in-the-1990s fans) or dealt with mix-blend-mode
or background-blend-mode
in CSS, you might be somewhat familiar with blend modes. When two layers or elements overlap while a blend mode is specified, a computation will occur at each pixel using the two elements’ RGB color values as inputs, resulting in a new color to display. There are many different modes like multiply
and hard-light
that each perform a different formula where elements overlap to visually present a different color.
Most of the time, I suspect, designers scroll through different modes to see what looks the best to them, perhaps additionally adding an opacity to the elements to soften the effects. But since every mode has a specific formula, it is entirely possible to understand the resulting color just by looking at your two source colors.
\nThe web follows sRGB for its default color space. This happens for interpolating colors in animations and gradients, and it makes the math fairly straightforward. Even if you specify a value in HSL (with hue, saturation, and lightness), the resulting color will be computed as a RGB color (red, green, and blue). More options are coming in the CSS Colors Level 4 and Level 5 specifications, and there is plenty of talk on the working group Github about how the future might allow us to interpolate along different models.
\nBut as of today, blend mode math happens once for the red channel, once for green, and once for blue.
\nWe will focus on multiply
for our example of this math, and then we will will talk about some key issues where the math doesn’t add up.
multiply
blend mode #Let’s take the color red
defined as #ff0000
. Each channel is a number between 0 and 255 (inclusive). This color has 255 in the red channel and 0 in the green and blue.
#ff0000
is equivalent to the function value of rgb(255,0,0)
. You can also use percentages in this function, so for the rest of the article we will be talking values of 0%
to 100%
instead of 0 to 255. With this notation, red is rgb(100%, 0%, 0%)
.
Let’s say we want to blend this with another color, and let’s choose yellow as rgb(100%,100%,0%)
To perform a multiply
blend mode we take the values of each element along each channel and multiply them. We do this in a 0 - 1 scale though (so 100%
is 1
and 50%
is .5
, etc.)
rgb(100%, 0%, 0%)
rgb(100%, 100%, 0%)
1 × 1 = 1
0 × 1 = 0
0 × 0 = 0
So our result, when converting back to percentages is rgb(100%, 0%, 0%)
… otherwise known as our original red source value!
What if we instead chose cyan (rgb(0%,100%,100%)
)?
rgb(100%, 0%, 0%)
rgb(0%, 100%, 100%)
1 × 0 = 0
0 × 1 = 0
0 × 1 = 0
Now, converting back to percentages, we get rgb(0%, 0%, 0%)
: black
Since we are multiplying values less than or equal to one, our multiplication blend mode will never result in a color lighter than we started. Each channel will stay the same as one of the sources or be darker (approach zero).
\nTo me, there is magic in values like red and cyan where the result is three zeroes… knowing how to get black from a multiply blend mode gives you a lot of power in controlling how your final design looks. And the math is fairly straightforward, so it’s reassuring in a world of uncertainty that two colors will always result in the same third color any time you blend them together.
\nUntil they don’t.
\nSo there is a thing with color spaces. Color management is one of those topics I’ve known about for a quarter century but never dug into how it all really works. Everything is a lot simpler when red is red and green is green and you don’t think harder than that. But thankfully several people do think about these topics and as a result our displays have become more vibrant and consistent.
\nAs noted earlier, when you specify a color such as rgb(100% 0% 0%)
on the web you are dealing with sRGB color space, and it’s the primary color specification that the web knows. There are other newer ways to specify color, such as through the color()
function, that are starting to be implemented, but basically if you want a bright red, you go rgb(100% 0% 0%)
. When you say you want a gradient from that red to blue (rgb(0% 0% 100%)
), the browser determines the in-between values in that same sRGB space and does the math by taking the red channel from 100 to 0 while simultaneously taking the blue channel from 0 to 100.
But different displays and monitors use different color spaces when they render out the color to the user. So, for example, on a Mac, you can open up your System Preferences > Displays > Color and find what display profile your computer uses. Chances are it uses something called Color LCD or iMac and ultimately what that means is when you specify rgb(100% 0% 0%)
for red on the web in a browser that uses the device color management scheme (such as Safari or Edge/Chrome) the browser technically uses a different R/G/B value to give you a similar red.
On an iMac I used recently (with the help of the Digital Color Meter tool), the color that is produced at the display level is not 255 0 0
but instead 252 13 27
Similarly my cyan (specified as rgb(0 255 255)
) came back as rgb(45 255 254)
.
This isn’t information that most designers or developers need to deal with, since again interpolation on the web happens in sRGB so the math for our gradient earlier still happens between the original values. The display system does its own computation to take those sRGB colors determined by the browser to the appropriate display values.
\nOkay.
\nNow here’s the twist.
\nMath doesn’t always happen in sRGB.
\nSpecifically… and you might have guessed it by now based on the amount of time I spent discussing blend modes…
\nBlend mode math in Safari, Edge, and Chrome happens in the display’s color space and not sRGB.
\n:Head exploding emoji:
\nIt means, as of today, I can only consistently specify colors in sRGB and with blend modes I can only get a result specified by the display color profile. Converting our previously discussed Mac display values for red and cyan into percentages we get:
\nrgb(98.824% 5.098% 10.588%)
rgb(17.647% 100% 99.608%)
And when Chrome or Safari performs multiplication blend mode with those values (that originally gave us rgb(0 0 0)
in sRGB):
.98824 × .17647 = 17.439%
.05098 × 1 = 5.098%
.10588 × .99608 = 10.546%
Which is certainly a dark color… but it is not black. The two source colors defined in sRGB result in a color defined in sRGB using math in a different color space.
\nSimilar combinations of colors that should equal black, such as yellow (rgb(100% 100% 0%)
) times blue (rgb(0% 0% 100%)
) also get values that are close to black... but not black.
I would argue this is not the expected behavior. Throughout the color/values/animation specifications it discusses how interpolation and math with mixing colors is done in the same default color space as colors are defined (sRGB). In the compositing spec, the only discussion of color spaces is in the “Non-separable blend modes” section, where the blend modes around hue and saturation are discussed.
\nAs mentioned earlier, in Firefox this is not an issue, though people say this is partly because Firefox does not support advanced color management.
\nAnother reason this feels like the incorrect way to handle the math is that it is not consistent with Canvas blending. If you use the same colors in a canvas
with the same blending, the result is solid black
even in Safari and Chrome.
But the biggest reason in my head is that if the only way we can really specify colors in one way with a fixed set of numbers… the math should respect those numbers we specify.
\nPeople who have a solid understanding of color management (in addition to colors on the web) are discussing ways to approach this in the future. Chris Lilley and others at the W3C have pushed for color management on the web for a long time, and have discussed options to allow for specifying the color space for a document, etc (thanks to Amelia Bellamy-Royds for pointing me to several related Github issues on the topic). Even in these areas it seems the consensus should be that sRGB is the default for compatibility and especially when you are using that to begin with. In the future when we have the ability to use lab()
or color(display-p3 100% 0% 0%)
which give us a different range of colors in certain color spaces, we might be able to assume a different default for interpolation and compositing. It will be fun to see where the newer levels of the color specification go with people like Chris, Lea Verou, Una Kravets, and Adam Argyle as editors on the spec.
A big thank you to Eric Portis for walking me through some of the Mac tooling (like the Digital Color Meter) and the basics of the monitor display profiles. That was integral to even start to understand why my visual results were not matching the math defined in the specification. He also started a small discussion on Twitter which verified some of our theories as to what was happening with the blend modes.
\n", "date_published": "2020-03-03T22:59:30Z" },{ "id": "https://danielcwilson.com/blog/2020/02/motion-path-transforms/", "url": "https://danielcwilson.com/blog/2020/02/motion-path-transforms/", "title": "How They Fit Together: Transform, Translate, Rotate, Scale, and Offset", "content_html": "The transform
property and its friends became more powerful through the addition of the new individual transform properties (translate
, rotate
, scale
) and the offset
properties (as a part of CSS Motion Path). All effectively provide a transformation for your element, and all have to follow specific rules to know how those transformations apply.
These new properties are now available in Firefox 72+. Motion path properties are also in Chromium browsers, and the independent transform properties are behind the Experimental Web Platform Features flag.
\nThe power of the transform
property is that it can hold any number of transformations. There can be multiple translations, rotations, scalings, skewings, and perspective changes. If you combine them in different orders, you can get different effects.
How do all these new properties interact with transform
and transform-origin
?
When you perform transformations, you technically are not modifying the element itself. You are affecting its coordinate system. So a rotation rotates the x and y axes. If you have a transform that rotates 45 degrees and then apply a translate 100 pixels to the right, the translation will not go to the true right. It will instead go to the right of the already rotated coordinate system, so it will be going down and to the right at the 45 degree angle.
\n\n See the Pen \n Visual Reference: Transform Coordinate Systems by Dan Wilson (@danwilson)\n on CodePen.\n
\naside {
rotate: 33deg;
scale: .85;
translate: 20px 50px;
}
aside.equivalent {
transform: translate(20px, 50px) rotate(33deg) scale(.85);
}
\nInstead of always having everything inside the transform
function, we can specify one translation, one rotation, and one scale separately. This can be cleaner or more readable for some codebases.
It also allows us to change one without overriding the other transformations.
\naside {
transform: rotate(10deg)
}
aside:hover {
transform: scale(.85);
}
\nAt first glance, you might expect this element to be rotated 10 degrees and then on hover add a scale down to the original transformation. But properties override, so the rotation is lost on hover. As different states or interactions become more complex it can be complicated to keep all the desired transformations straight.
\nBut with the independent transform properties, we can add or remove individual transformations without affecting the others. So we can slightly alter our code and get a different result where the hover state is both rotated and scaled.
\naside {
rotate: 10deg
}
aside:hover {
scale: .85;
}
\nWe can also transition
or animate each individually. In this example, the scale and translation change on hover with different durations and delays. The rotate does not transition but instead stays unaffected by the other two properties animating.
\n See the Pen \n Independent Transform Properties by Dan Wilson (@danwilson)\n on CodePen.\n
\nThese properties do not take a list of options like the original transform
property, so you only get one of each type per element. If you want more than that, you will need return to the transform
.
They also all share the same transform-origin
, so whether you are using the transform
property or the three individual properties they all depend on transform-origin
.
These properties are always applied in the same order, and they happen before everything in the transform
property:
translate
rotate
scale
\n See the Pen \n Visual Reference: Order of translate, rotate, scale by Dan Wilson (@danwilson)\n on CodePen.\n
\nIf you want to have the rotation applied before a translation, you will need to use the transform
property. Either of the following options would support this scenario.
aside {
transform: rotate(30deg) translate(10px,10px);
}
/* These are still separate, so you still can work independently */
aside.alternate {
rotate: 30deg;
transform: translate(10px,10px)
}
\noffset
properties mentioned at the beginning? #With the transform
property we can translate, rotate, scale, and skew our element as we choose, and the offset
properties also effectively translate and rotate our element.
Even though it works in a different way, the offset
properties all are effectively applying transformations on the coordinate system, in a similar manner to the transforms. As such, they also rely on the same transform-origin
that the other four transformation properties use.
offset
properties applied? #The three new independent transform properties happen before the offset properties. The transform
functions are applied in order after the offset
.
translate
rotate
scale
offset
(distance, anchor, and rotate)transform
(functions applied in the order specified)So for example, using a basic offset-path
animation will produce a different visual result when you combine it with a transform: translate(25px -35px)
versus combining it with a translate: 25px -35px
.
\n See the Pen \n transform:translate vs translate with offset-path by Dan Wilson (@danwilson)\n on CodePen.\n
\n(I also have a rotate example with transform-origin
thrown in for good measure if you so please).
It is certainly interesting (and often confusing) how these all interact with each other. Knowing the order of transformations, though, is at least half the struggle in clearing up some of the transformation magic happening with transform
, independent transform properties, and Motion Path.
With the release of Firefox 72 on January 7, 2020, CSS Motion Path is now in Firefox, new Edge (slated for a January 15, 2020 stable release), Chrome, and Opera (and other Blink-based browsers). That means each of these browsers supports a common baseline of offset-path: path()
, offset-distance
, and offset-rotate
.
Firefox also released stable support for offset-anchor
(currently behind the "Experimental Web Platform Features" flag in Blink-based browsers). To celebrate, let’s review the basics of what is supported, explain some of the specifics when a path is applied, and also highlight the possibilities of offset-anchor
.
\n See the Pen \n Shape Revealer 2020 by Dan Wilson (@danwilson)\n on CodePen.\n
\nThere technically is no motion involved when using any of the properties defined in the CSS Motion Path specification. The offset-path
property allows you to set up an invisible path that an element can follow.
#my-element {
offset-path: path('M0,0 C40,160 60,160 100,0');
}
\nIf you are familiar with SVG paths, or have explored other places to use the path()
function like newer clip-path
options, this might seem familiar. This collection of letters, numbers, and commas is a way to specify vectorized lines, curves, and more. This previous example makes a U-shaped curve starting at 0px,0px
and ending 100px to the right at 100px,0px
. To learn more about this syntax, check out Joni Trythall’s SVG Pocket Guide or the Illustrated SVG path
Syntax Guide on CSS-Tricks.
Some quick basics:
\nM 0,0
means move to the x, y
coordinates of 0, 0
without drawing anythingL 10,10
means draw a line from the previous point to 10,10
C
and three sets of coordinates.There are many other ways in the CSS Motion Path specification to set an offset path, such as using other CSS Shape functions like circle()
, the element’s box, or the new ray()
function that introduces polar coordinates to CSS. However, at this time, only path()
is supported in all the browsers already mentioned. There is significant progress made with ray()
behind flags, so it is likely to be the next to be released.
The offset distance will be any length value, though I would wager that percentages are the most valuable as 100%
always represents the end of the path. So going halfway along the path is as straightforward as saying 50%
.
As stated earlier, the CSS Motion Path spec actually does not provide motion out of the box. We take our path definition and then use the existing tried and true animation methods to animate the value of offset-distance
. So to animate an element along its entire path from 0%
(the default value of offset-distance
) to 100%
we can set up a CSS animation (or use the Web Animations API, requestAnimationFrame
, etc.) to modify these values.
#my-element {
offset-path: path('M0,0 C40,160 60,160 100,0');
animation: go 4000ms infinite ease-in-out;
}
@keyframes go {
100% {
offset-distance: 100%;
}
}
\n\n See the Pen \n Offset-distance in motion by Dan Wilson (@danwilson)\n on CodePen.\n
\nWe can also change the path itself, and if it has the same number of data points the brower can interpolate the values and smoothly transition the path. If we start with a simple straigh path defined as path('M0,0 L100,100')
(which defines a line segment from 0px,0px
to 100px,100px
) we are able to animate it to a new line segment as long as it also has only two points, such as path('M100,0 L0,100')
. If you try to go to path('M100,0 L0,100 L100,100')
the browser will not be able to determine the in-between values since the paths have a different number of points, so the path will simply toggle between the two definitions instead of smoothly transitioning.
\n See the Pen \n Animating offset-path by Dan Wilson (@danwilson)\n on CodePen.\n
\nBy default, the element will "face" the direction of the path, with its right side staying perpendicular to the path at all times. This is thanks to the auto
value of the third offset property offset-rotate
. If we would rather the element face away from the direction of the path we can use the value reverse
.
The offset-rotate
property also takes angle values if you want to have the element stay in a fixed direction at a certain angle and not follow the path. With 0deg
, the element will stay in its original, non-pathed orientation where the right side always continues to face the right regardless the direction of the path.
The last option is to combine the keywords auto
or reverse
with a second option of an angle. By setting, for example, offset-rotate: auto 90deg
the element will rotate to account for the direction of the path, but it will have an added offset of 90 degrees. So by rotating the element a quarter turn, it is now the top side that faces the direction of the path and remains perpendicular to it.
\n See the Pen \n offset-rotate vs. transform/rotate by Dan Wilson (@danwilson)\n on CodePen.\n
\nJust as offset-path
and offset-distance
are animatable, when using angles (even in combination with auto
/reverse
) the offset-rotate
property can be animated.
Elements will be centered on the path, but we can change this by setting the offset-anchor
property. The types of values are similar to what you see in background-position
or a two-dimensional transform-origin
where you set the horizontal (x-axis) position and the vertical (y-axis) position. So the default value is offset-anchor: 50% 50%
which can also be specified as center center
. We can do the top left
corner (same as 0% 0%
) or any other length/percentage value to change the point on the element that anchors to the path. It is even possible to use values outside the bounds of the element with negative numbers or percentages beyond 100%, for example.
\n See the Pen \n Offset Path Anchor by Dan Wilson (@danwilson)\n on CodePen.\n
\nWhen the values are lengths or percentages, the anchor property can be animated. Therefore, each of the four offset
properties can be animated even though the only one that moves along the path in the traditional sense is offset-distance
.
\n See the Pen \n animating offset distance, rotate, and anchor by Dan Wilson (@danwilson)\n on CodePen.\n
\nYou might suspect that nothing really happens when you specify a path alone, as it is the distance, rotation, and anchor that affect how an element appears along the path.
\nThe path itself has its 0,0
coordinates placed at the element’s original placement in the document flow. When specifying only an offset-path
, our element is placed at the start of our (invisible) path (offset-distance: 0%
). It is very likely the element will visually look different, though, as it is the element’s center point that is placed at the start of the path (offset-anchor: 50% 50%
), and its original right side will face the direction of the path (offset-rotate: auto
).
If the start of the path is at the top left corner then at a minimum the element will be shifted up and to the left such that the anchor point (at center center
by default) is at the top left corner of the element’s original position. If you would rather have the element stay in its initial position at the start, you likely will need to rework the path definition to start at your initial anchor point or work some magic with position
or the independent transform properties.
Those are some key basics, but there are certainly many possibilities to explore. Motion paths are often associated with continuous curves, but there is nothing that says you cannot use straight lines or disconnected paths. Motion paths are typically associated with, well, motion, but offset
can provide a unique way to simply position elements statically. The time to explore their possibilities is now.
Even though the hsl()
color function allows us to specify hue, saturation, and lightness levels individually, there is no straightforward way to animate one value independently of the others. We will explore how color interpolation works on the web, look at our options to animate a hue around a color wheel, and see how Houdini gives us a mechanism to do this.
HSL
#The beauty of defining a color with hsl()
is that you can start with a hue and then tweak it. If you want a reddish color you use a hue near 0
, or a cyan color is on the opposite side of the wheel at 180
(the values represent degrees in a circle, so the range of different values are from 0 to 360). The other two values are percentages from 0%
to 100%
. Saturation shows how full the color is at 100%
or grayed out more as you approach 0%
. Lightness at 0%
is always black, 100%
is always white, and 50%
is a good default for getting a bright color.
\n See the Pen \n Visual Reference: Colors via HSL by Dan Wilson (@danwilson)\n on CodePen.\n
\nInterpolation is effectively how the values between a start and end state are determined during an animation. Browsers are very efficient at determining the values between two numbers such as filling in an opacity animation from 0
to 1
.
Determining what colors to show between two color states in an animation is actually also straightforward for a browser to accomplish, as colors boil down to a set of numbers.
\nWith RGB colors like #ffaabb
and rgb(255,170,187)
we have three sets of numbers that can be interpolated individually to determing a color value at each step in the animation. So if we want to move from a magenta (rgb(255,0,255)
) to a cyan (rgb(0,255,255)
) the R value will animate from 255 to 0, the G value will go from 0 to 255, while the B value stays at 255. This process is also how the inbetween values are determined for gradients.
\n See the Pen \n Color Interpolation by Dan Wilson (@danwilson)\n on CodePen.\n
\nEvery step of this transition is still very much a blend of these two colors. With hsl()
we get something a little more interesting where you can change a single value (the hue) and potentially get every color of the rainbow. Since we were able to interpolate each of the three RGB parts, you might think the three individual parts of the hsl()
will interpolate on their own as well and we could actually rotate around the color wheel simply by animating from hsl(0,100%,50%)
to hsl(360,100%,50%)
.
\n See the Pen \n Animating from red to red by Dan Wilson (@danwilson)\n on CodePen.\n
\nThis red block is technically animating, I promise. However, regardless how you define your color, CSS always animates between its computed values in RGB in the sRGB color space. So even though we used HSL in the previous example, it technically is doing an animation from #ff0000
to #ff0000
. You can edit the example to instead go from a hue of 0
to a hue of 180
(cyan) to see it doing the same animation you would find by going red
to cyan
or #ff0000
to #00ffff
.
So whether you use named colors, RGB, HSL, or future methods like hwb()
, the color stops are effectively converted to RGB and then have the interpolation applied.
Is it possible to animate a hue change only?
\nhue-rotate
#It is! Sort of!
\nCSS Filters have been well supported for several years now across the board, including non-Chromified-Edge. Most people tend to lean on filters for the blur()
function, but there are several other options including hue-rotate()
.
This will take the current hue of your color and rotate it around the color wheel the angle of degrees you specify. So let’s try our animation again with a starting hsl(0,100%,50%)
and animate the hue to 360
via hue-rotate(360deg)
.
\n See the Pen \n Animating from red to red (hue-rotate) by Dan Wilson (@danwilson)\n on CodePen.\n
\nWe did it!*
\nWith an asterisk!
\nMy goal here was to keep the saturation and lightness consistent and then change only the hue from 0
to 360
. The hue was accurately rotated around a color wheel, but if you look closely at the color changes, we should see colors very similar to the perimeter of the circle in the first example of this article, where we can very clearly see each bright color from a rainbow — red, orange, yellow, green, blue, indigo, and violet. This animation clearly has a red, green, and blue, but everything in between is much darker and muddier than I’d expect.
That is because hue-rotate()
does not use the same color wheel as you get with hsl()
(as Amelia Bellamy-Royds wonderfully explains in this blog comment). It gets complicated with color spaces and the fact that hue-rotate
uses a combination of hue, saturation, and luminance instead of lightness.
\n See the Pen \n What I Want out of hue-rotate by Dan Wilson (@danwilson)\n on CodePen.\n
\nSo this gets us a more interesting color animation, but it is not a rainbow animation at the same saturation and lightness. We can approximate a true hue rotation by using multiple keyframes with different colors. For example we get a little closer if we set an animation with three states of hsl(0,100%,50%)
, hsl(120,100%,50%)
, hsl(240,100%,50%)
. We get even closer as we do five keyframes and ten keyframes. With 36 frames we get an approximation that is very close to the desired result.
\n See the Pen \n HSL: Options to Rotate H with Same SL by Dan Wilson (@danwilson)\n on CodePen.\n
\nIf you are in a browser that supports the Houdini Properties and Values API specification, you will see an extra animation in the previous example (try a Blink browser with Experimental Web Platform Features flag enabled or the latest Safari Technology Preview). That gives us the actual behavior I desired as it accurately keeps the saturation and lightness in place and interpolates the hue value between 0
and 360
.
As we can use Custom Properties to individualize the parts of a value, we can work with a base hsl(var(--hue),100%,50%)
color and modify our --hue
to go from 0
to 360
. Custom Properties themselves are not animatable out of the box, but Houdini allows the web author to provide a syntax for a specific custom property which tells it the rules to interpolate the property’s value.
With the following CSS, we are changing the hue from 0
to 360
. The JavaScript is telling the browser to treat our custom property --hue
as a number, and so it can follow the interpolation rules for numbers.
div {
--hue: 0;
background: hsl(var(--hue), 100%, 50%);
animation: hue-rotate 10000ms infinite linear;
}
@keyframes hue-rotate {
100% {
--hue: 360
}
}
\nif (window.CSS && CSS.registerProperty) {
CSS.registerProperty({
name: '--hue',
syntax: '<number>',
inherits: false,
initialValue: 0
});
}
\nAnd with that we get a true hue rotation that maintains the same saturation and lightness found in our hsl()
root color. We can similarly animate along saturation and lightness by setting up custom properties for each and registering the properties in JavaScript with the <percentage>
syntax and an initial value that is a percentage, such as 100%
.
\n See the Pen \n HSL Houdini by Dan Wilson (@danwilson)\n on CodePen.\n
\n", "date_published": "2019-09-14T23:59:30Z" },{ "id": "https://danielcwilson.com/blog/2019/04/optical-fun-3d-glasses/", "url": "https://danielcwilson.com/blog/2019/04/optical-fun-3d-glasses/", "title": "3D Glasses with Perspective Origin", "content_html": "This is the fifth article in a series discussing different optical illusions & mechanical toys and how we can recreate them on the web (and learn from them). Unlike the other articles in this series, the full effects require a physical item — specifically a pair of 3D Glasses with the red and cyan lenses or a Google Cardboard.
\nThe classic-looking red-cyan 3D glasses have been around for about a century, and we can build their corresponding images on the web with a little help from 3D Transforms, perspective, and blend modes.
\n\n See the Pen \n 3D Glasses / Google Cardboard Toggle - Black on White by Dan Wilson (@danwilson)\n on CodePen.\n
\nA few years ago, Una Kravets wrote a great article about how you can use blend modes to create your own 3D images - known as anaglyphs. We can take two images, and apply a cyan screen to one and red to the other. These images are then overlayed. The 3D glasses force one eye to see one image, the other eye sees the other image, and the brain does its magic to create apparent depth.
\nThe key to a successful anaglyph is a change in perspective for the images. You can take an photo with a camera having the viewfinder at your left eye (assuming that your camera still has an eye-level viewfinder). Then you can move the viewfinder to your other eye and take the photo again. Your two images will be fairly similar except a slight change in perspective has happened as each eye has a slightly different viewing angle of your subject.
\nThese images can then be placed on top of each other with the appropriate red and cyan screens applied. How far apart the different colors are at given points in the image determines the perceived depth.
\n\nWe don’t even need a camera, because on the web we have 3D Transforms... with built-in perspective. As objects in a line get further away (such as with a negative translateZ
value) they appear smaller and ultimately resolve towards a vanishing point, giving us perspective for a given scene.
Individual elements can share the same 3D space, and they can therefore also share the same perspective
. We can set our perspective
property with a length value that ultimately shows how exaggerated our 3D depth effect is. By default our effective vanishing point is in the center of the 3D space. However, we have ways to control this point via the perspective-origin
property. With the exact same transform
value we can get very different looking scenes by changing only our perspective-origin
.
Much like we can take two photos from a slightly different perspective, we can create two scenes and adjust the perspective slightly. We can set up two containing scenes — one for our left eye and one for our right eye. We will give each scene the same perspective
value, and each object within each will have the same transforms applied as their duplicated counterpart in the other scene. All objects can share their respective scene’s shared 3D space via transform-style: preserve-3d
.
The only difference in the compososition of the scenes will be the perspective-origin
. We can set one to be slightly greater than center horizontally, and the other to be slightly less. With CSS Variables (Custom Properties):
.scene {
perspective-origin: var(--origin-x) 50%;
perspective: 20vmin;
--diff: 6px;
--origin-x: calc(50% - var(--diff));
}
.scene:nth-of-type(2) {
--origin-x: calc(50% + var(--diff));
}
\nThe 6px
is a value I chose after some experimenting, though there is surely a more mathematical way to choose that based on distance between the eye and the screen. Other values produce slightly different effects, so it’s worth exploring the difference with other values.
Now that we have two overlapping scenes with a slightly different perspective, the second biggest piece of the puzzle is making sure each eye can only see the appropriate scene through the 3D glasses. It is time to create the anaglyph. We will assume our glasses have the red lens on the left eye.
\nWhich color goes where depends on if you want to create a dark foreground with a light background or the inverse.
\nFor starters, let’s assume we have black outlines on a white background. If you are using basic shapes and outlines you can set the red
and cyan
colors directly on your elements. The elements in your left scene get cyan, the right ones get red. Then we apply a mix-blend-mode: multiply
to the scenes. This will make any area where the red and cyan overlap turn black. When looking through the red lens, your eye will be able to see anything that is black or cyan, and your other eye will only see the red and black through the cyan lens. This relies on the same blend mode math as the Red Reveal lens effect.
\n See the Pen \n 3D Glasses / Google Cardboard Toggle - Black on White by Dan Wilson (@danwilson)\n on CodePen.\n
\nIf you want to have a white foreground on a black background, the colors are reversed — red on the left and cyan on the right. We also change our blend mode to a screen
as it similarly looks at where cyan and red overlap and turns those areas white. Now your cyan lens will reveal everything that is cyan or white, and the red lens will reveal everything that is red or white.
\n See the Pen \n 3D Glasses / Google Cardboard Toggle by Dan Wilson (@danwilson)\n on CodePen.\n
\nIf we want to work with more than just black and white, we can introduce additional blend modes. For a dark background, we will apply a cyan screen over our left scene, and a red screen over the second scene. This can be achieved with a cover for each scene of the given color and a mix-blend-mode: screen
.
Then, to make sure the bottom image still shows through, we apply a mix-blend-mode: multiply
to the whole top scene. This combination of blend modes lets you still make out how the original scene is supposed to look, and it gets that classic every-object-has-some-red-and-some-cyan look. The additional blend mode math in this case is similar to the discussion in constructing a Barrier Grid animation image.
.scene::after {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
background: cyan;
mix-blend-mode: screen;
}
.scene:nth-of-type(2)::after {
background: red;
}
.scene:nth-of-type(2) {
mix-blend-mode: multiply;
}
\n\n See the Pen \n 3D Glasses: Radical Mathematical - Basics by Dan Wilson (@danwilson)\n on CodePen.\n
\nSince we are already positioning everything in our scene with 3D Transforms, we can even animate our scene and take further advantage of the perspective. Performance can be affected as we are doing duplicate animations in a 3D space, and those animations are happening behind blend modes.
\n\n See the Pen \n 3D Glasses: Radical Mathematical by Dan Wilson (@danwilson)\n on CodePen.\n
\nEven if you are unable to find a place to sell you cheap 3D glasses, you are in luck if you own a not-as-cheap-but-still-not-exorbitant version of Google Cardboard. It uses similar alignment of images based on altering viewing angles between the eyes. Except here the images are not overlayed, they appear side by side. This example uses the same perspective origin offset, and presents the two scenes side by side. They seem identical while they are apart, but the minor offset is just enough to give an additional illusion of depth when palced inside a Cardboard device.
\n\n See the Pen \n Google Cardboard: Cube by Dan Wilson (@danwilson)\n on CodePen.\n
\nRest assured: If as a society we figure out how to fit two tiny web browsers inside a classic View-Master reel then I will update this article as it could use similar perspective origin magic.
\n", "date_published": "2019-04-15T12:30:30Z" },{ "id": "https://danielcwilson.com/blog/2019/02/step-and-jump/", "url": "https://danielcwilson.com/blog/2019/02/step-and-jump/", "title": "Jumps: The New Steps() in Web Animation", "content_html": "The first big change to web animation easings (aka timing functions) is upon us. After some discussions, updates to the specification, and initial implementations as a separate function, Firefox 65 introduces four new options for the steps()
function.
jump-start
jump-end
jump-both
jump-none
\n See the Pen \n Visual Reference: Steps() and Other Easings, Translate by Dan Wilson (@danwilson)\n on CodePen.\n
\nHow do these new values compare to what we already had, and when are the best times to use each one?
\nsteps()
function #First, let’s take a step back and discuss what easings even are and what the steps()
function allows us to do.
Easings allow us to change how a transition
, CSS animation
, or Web Animations API animation completes over time.
.mover {
animation: move 2000ms;
animation-timing-function: linear; /* easing */
transform: translateX(0px);
}
@keyframes move {
100% {
transform: translateX(200px);
}
}
\nWith a linear
easing, everything moves at a steady pace. If we change that to ease-in
we will have something that starts slower and speeds up as it gets to the end of its animation.
Steps are a bit different, as we can tell the animation to have a specific number of distinct frames. So changing the easing to steps(2)
, for example, would give an animation with only two states, a starting position and an ending one.
How steps()
determines each step interval is based on a second (and optional) parameter. This is where our new values come into play and join the two existing values — start
and end
.
Instead of diving into what start
and end
mean, let’s cut to the chase to say that two of the four aforementioned new values are in fact aliases of these original values:
jump-start === start
jump-end === end
The jump
prefix helps us explain the words start and end more effectively. When we use start
or jump-start
we are telling the calculation to skip the starting position. With end
/jump-end
we want to skip the ending position.
You can think of the steps(n)
function as taking snapshots of an animation with a linear
easing at the specified intervals and displaying that snapshot until it is time to show the next snapshot. So when we say we want steps(4, jump-end)
we will get an animation that divides our animation into four sections and snapshots the initial position of each of those fourths. With steps(5, jump-start)
we get an animation divided into five parts, and we take the final position in each part.
\n See the Pen \n Visual Reference: Jump Start, Jump End, Linear by Dan Wilson (@danwilson)\n on CodePen.\n
\nWhy would you want to skip a beginning or ending state? We are telling the browser to go from a specific state to a different specific state with our animation keyframes, so wouldn’t we always want both those states represented in our resulting animation?
\nIt becomes clearer to realize the benefits of skipping a start or end when you think about the seconds hand on a clock — we’d probably want an animation running for 60 seconds for a full rotation (0deg
to 360deg
), and an easing of steps(60)
. This gives us a clock with a second hand that steps to each mark on the clock (jump-end
/end
is assumed when no second parameter is specified). Without the jumping of the end state, we would have an animation where the start and end would be at the top (0deg
), and thus our clock would not be natural as it would stay at the top for two seconds and do the other 58 seconds in between.
\n See the Pen \n Visual Reference: Steps() and Other Easings, Full Rotation by Dan Wilson (@danwilson)\n on CodePen.\n
\nAnother important reason is animating with sprites. You can have a strip of frames to animate and transform the position from translateX(0)
to translate(-100%)
(or use background positions, etc). If you translate a full 100%
of the width, the final state will be out of view, so we again jump the end, and we can magically capture each frame.
\n See the Pen \n Visual Reference: Steps + Sprites by Dan Wilson (@danwilson)\n on CodePen.\n
\nThis keeps us from having to do extra math to prevent the final (blank) frame from showing.
\nAh, yes. The new stuff.
\nSometimes skipping a state really is not what we are looking for. The new option jump-none
allows an animation that does not jump the start or the end. For any animation with a step count of at least two, the begining state and the ending state will be represented. The remaining steps will be distributed evenly between. Three steps will have their effective snapshots taken at 0%
, 50%
, and 100%
.
A straightforward case for this option is moving an object across a screen. We might want to move an object from point A to point B with a stepped effect. Previously with only the jumping of start
or end
available, there was not a straightforward way to tell the animation to show the starting and ending positions with equal frames in between. The addition of jump-none
now gives us this ability.
\n See the Pen \n Visual Reference: jump-none vs linear easing by Dan Wilson (@danwilson)\n on CodePen.\n
\nWith the old ways of steps()
, you would usually still be able to achieve this, but you would need to do some extra math and make the translation technically go beyond your visual start or end state. Now it is more straightforward as you can be confident your start and end states are what you explicitly make them.
Opacity also can benefit from making sure the start and end states are always visible. Say we want to do a fade out via a stepped opacity animation from 1
to 0
. With jumping start
or end
, either the fully opaque or the fully transparent state will never be seen. But jump-none
make sure both are seen. An animation with steps(2, jump-none)
will have a straight on/off animation (to create a clean strobe), and steps(4,jump-none)
will give us opacities of 1
, .6667
, .3333
and 0
.
\n See the Pen \n Visual Reference: Steps() with Opacity by Dan Wilson (@danwilson)\n on CodePen.\n
\nWe’ve jumped starts, we’ve jumped ends, and we’ve jumped neither one, which leaves us with jumping both.
\n\n See the Pen \n Visual Reference: jump-both vs linear easing by Dan Wilson (@danwilson)\n on CodePen.\n
\nI will be interested to see the use cases for this animation. On the one hand it allows for completeness (since we are adding a none
, we might as well add a both
), but it also has potential as easing options might be used outside animation. The use cases for using easings in the context of gradients seem more compelling (to me, in the moment I write these words), than what jump-both
provides in the context of animation.
Chrome already implemented the jump-none
behavior under the older frames()
spec discussion, so I suspect it will not be a huge lift to move it into the new naming. Webkit and EdgeHTML do not have it in any preview versions yet. So it is time to familiarize and experiment rather than go all in without fallbacks.
I had the pleasure to write the 20th article for 24 ways this year with Clip Paths Know No Bounds — thinking outside the box when using the clip-path
property.
clip-path
polygonThis is a repost of the September 2018 Edition of the Animation at Work newsletter where I was Guest Editor.
\nI love exploring the next level of tools we have available to build interactive animations on the web. With Chrome, Firefox, Safari, and Edge giving us early access to their Canary, Nightly, Technology Preview, and Insider Preview versions respectively, we can start exploring features that are on the path to stable browsers. Often we get to see how the standards even evolve as they start iterating on implementations.
\nHere are some of the _ new things in browsers _ that I’m most excited about, centered around web APIs, CSS Houdini, and Canvas.
\nThe Web Animations API (WAAPI) is coming to WebKit/Safari, and it’s further along than you might realize.
\nSafari will join the existing WAAPI implementations in Firefox and Blink browsers, so if you haven’t already, it might be time to start exploring it. For the best starting point, check out the guide on MDN from Rachel Nabors.
\nIf you want to dive into some of the future features (already in Firefox Nightly), you can explore how the WAAPI will let you smoothly modify already running animations.
\nVincent De Oliveira’s ever-growing Houdini playground is here to introduce you to many different facets of CSS Houdini. You can learn the basics (and the more-than-basics) around its different specs/APIs.
\nCSS Houdini allows us to build on the power of Custom Properties (CSS Variables) by letting us animate them directly inside keyframes, as shown by Ana Tudor animating gradients and transform functions individually.
\nThere are more people exploring the possibilities of animating Custom Properties in this collection of demos on CodePen.
\nEven fonts are getting into the animation game in new ways with Variable Fonts as Mandy Michael demonstrates in her impressive experiments.
\nFor Canvas drawing, we have a new OffscreenCanvas option to offload work to a worker and keep our main browser thread free for other logic/interactions. And it pairs nicely with progressive enhancement and feature detection while it makes its way into more browsers.
\nSpeaking of sending work away from the main thread, have you seen that Houdini is introducing an Animation Worklet, and it includes initial implementation discussion points around scroll-based timelines?
\n", "date_published": "2018-10-06T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2018/09/optical-fun-morphing/", "url": "https://danielcwilson.com/blog/2018/09/optical-fun-morphing/", "title": "Morphing Images with Lenticular Printing: Illusions on the Web Part 4", "content_html": "This is the fourth in an article series discussing different optical illusions & mechanical toys and how we can recreate them on the web (and learn from them). The end result of this article can be seen as a standalone, fullscreen demo or with the code and result side by side.
\nPerhaps you’ve seen them when you collected trading cards in the 90s, more recently seen them on Bluray covers, or have them on that Best Buy gift card that you never used. As your viewing angle changes (or you physically rotate the item), an image morphs into a new one, a 3D effect might be achieved, or a small animation may occur. Sometimes referred as tilt cards or holograms, the approach we are recreating today is more formally associated with Lenticular printing.
\nThe way these physical cards are made involves a special printing surface with thin rows of lenses that affect what you see from a given angle. When you see one up close it is clear there are a series of lines that create the magic, and depending on your angle you might be able to see chopped versions of multiple images at a given time.
\nWhen moving this to the digital realm, we don’t really have a concept of the lens to affect what you see. But the web does have ways to split an image into smaller chunks and layers of 3d transforms to affect our viewing angle.
\nSee the Pen Lenticular Card: Noninteractive by Dan Wilson (@danwilson) on CodePen.
\nI am exploring the morphing between two images in this example. Starting with two images on top of each other, we then split both of them into tiny strips three pixels wide. I usually go with viewport units for something like this so that the central demo will scale nicely, but in keeping track of the width of the strips, I opted for fixed pixels everywhere. Let’s call this container that holds the two images our "card."
\nWe can split each image a number of ways, but here it is structured as an unordered list and each strip is a list item. Since there are only two images, we can take advantage of each list item’s pseudoelements. Each list item in the ::before
sets one of the images as its background, and the other image is similarly set in the ::after
. Custom Properties (CSS Variables) then allow us to offset the background-position
for each list item so that the result is the full image.
See the Pen Lenticular Card: Split Images by Dan Wilson (@danwilson) on CodePen.
\nThis is a fairly manual approach to show what all is happening. To keep things cleaner we could use Pug or another templating engine to simplify the markup and assign an index as a Custom Property that we could use to drive the background-position
. Also, there is the new Splitting.js library that generates elements and helpful Custom Properties from a single element with an image.
Now that we have two images split into thin columns and on top of each other we can start working with angles and perspective. With a perspective set on the body, and a transform-style: preserve-3d
down through the ul
and each li
we can know that all our 3D rotations will work in relation to each other (inside the same 3D space).
Since we do not have a direct concept of the fancy thin lenses in our browser, we will try to achieve something similar by rotating each strip 60 degrees around the Y axis away from us (with each pseudoelement rotating from opposite sides of the strip). We can make a blended photo with little triangles where a little of each image strip is seen at a given time.
\nSee the Pen Lenticular Cards: Zoomed by Dan Wilson (@danwilson) on CodePen.
\nli::before, li::after {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
}
li::before {
background-image: url(https://images.unsplash.com/photo-1473580044384-7ba9967e16a0?ixlib=rb-0.3.5&q=85&fm=jpg&crop=entropy&cs=srgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ&s=53a6022c048f8595d90636e94b7fabf3);
transform: rotateY(-60deg);
transform-origin: 100% 50%;
}
li::after {
background-image: url(https://images.unsplash.com/photo-1522735555435-a8fe18da2089?ixlib=rb-0.3.5&q=85&fm=jpg&crop=entropy&cs=srgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ&s=d404d2ca601ddb2b3b33c4e3f41291b4);
transform: rotateY(60deg);
transform-origin: 0% 50%;
}
\nNow that we have our base card constructed (such that a little of each image is shown when we look at the card straight on) we can talk about perspective. Our perspective-origin
is set to be in the center of the body
, and as we rotate the card around the Y axis at its center we can achieve our morphing image effect. As we rotate in one direction more of image A is covered up, revealing more of image B. As we switch and rotate in the other direction image B gets more covered up, showing more of image A.
We are only moving the card itself. There are many transforms used to create the structure of the card, but the only element that moves is our card (the unordered list).
\nIn between we even get a kind of sheen effect because of all the strips fighting for attention and the renderer trying to get the right pixels to display. Our final images are not perfect (you can see artifacts and some lines between strips), but the same is true when printed in the physical world.
\nSee the Pen Lenticular Card by Dan Wilson (@danwilson) on CodePen.
\nYou can use mouse/stylus/touch if your browser supports Pointer Events (sorry, Safari users) to rotate the card by dragging horizontally.
\nYou can also load the full demo up outside of an iframe to use on a device supporting deviceorientation events (you are welcome, Safari users). It is best to lock the device orientation.
\nThere are several ways to swap between two images. One could overlay two img
elements and fade the top one in and out with an opacity
animation.
See the Pen Lenticular Card: But Not by Dan Wilson (@danwilson) on CodePen.
\nOr in certain browsers you can explore doing something similar directly with layered background images. There is a cross-fade
function that can let you have two images mixed together such that the top one has a partial opacity, but its support is limited to earlier specs and Webkit currently.
This is the third in an article series discussing different optical illusions & mechanical toys and how we can recreate them on the web (and learn from them). Due to the nature of the methods discussed here, some animations will be fast, fill large screen space, and/or be shaky. All animations are paused by default.
\nIn the 19th Century, many toys were created to take several static images and trick the mind into animating them by flashing elements between them. We will explore a few of these toys, recreate them on the web, and then look at some other related tricks we can do in the digital-only realm.
\nAll of these tricks in the physical world rely on a collection of images moving rapidly and some device that creates a stroboscopic effect to keep the mind from simply blurring the fast images. This effect could be achieved from a barrier with slits that alternates between showing you the images beneath and not, or in modern times with a strobe light. By coordinating the speed of the images with the timing of the stroboscopic effect, you can essentially keep focus in one area and the images will animate in a similar fashion to a film projector.
\nPerhaps the best example of this approach (at least that is still remembered in the 21st Century) is the Zoetrope.
\n\nThe basic construction of a Zoetrope consists of a cylinder with a set number of slits, usually around an 1/8th of an inch. Additionally there are the same number of images on the inside of the cylinder. If you move the Zoetrope at an appropriately fast speed and focus on one section, you can see a looping animation with as many frames as there are images.
\nWithout the outer cylinder, you will just see the images spinning around in a blur. The barrier breaks the images up much like a flipbook. Here we build this on the web using 3D Transforms (with some CSS Variables) where you can toggle the barrier on and off to see the difference.
\nSee the Pen Zoetrope (Smiley) by Dan Wilson (@danwilson) on CodePen.
\nIn a similar vein, 3D Zoetropes often take images or sculptures and place them around a circle as well, but instead of a barrier cylinder to create the flipping effect, a strobe light is used. One of the more recent examples of this approach is with Toy Story.
\n\nThe timing of the strobe and the spin can be so precise that characters can stand perfectly in place if desired.
\nWe can recreate this on the web as well, with an added bit of glitch due to pushing the limits on framerates. We first create a barrier div of solid black that covers the entire page and toggle its opacity from 1
to 0
as fast as possible. Then we spin our Zoetrope built via 3D Transforms to match the strobe. If we have twelve frames, and our strobe is in the on
state for X milliseconds (and also in the off
state for X milliseconds) we will want one full rotation to take (12 × 2 × X)ms
so that the on and off states can be activated during each frame.
See the Pen 3D Zoetrope Final by Dan Wilson (@danwilson) on CodePen.
\nTricking our eyes with the physical toys is a little different than we do it on a computer. In 2018, we are most likely getting at best 60 frames per second on the web with our animations, so we can get a frame every 16 2/3 milliseconds. The browser and the eye can work together and do its typical “blur things things that go fast” most of the time.
\nHowever, we can take our Zoetrope based in its 1860s roots and update it for the computer age by trying to match its rotation to the 60 frames per second ideal. So if we have a 24 frame animation, we can spin our Zoetrope a full turn (without its outer cylinder) at 24 × 16 2/3
milliseconds (which equals 400ms) and get a similar, but clearer, animation effect.
Even without the outer barrier, it might still shake a little or stutter, especially on lower powered devices… but that’s half the fun of animating on the web in the style of the 150 years ago.
\nThis approach can be extended to other related moving picture toys like the Phénakistiscope. This was a flat circular toy that had images around the edge and slits in the middle of the circle. If you held the images towards a mirror and looked through the slits as you spin, you’d see a similar animation as our Zoetropes. But with our “spin it as fast you can” technique we can get rid of the slits and mirrors to spin the CSS Phénakistiscope circle around every 16 2/3 × number of frames
milliseconds.
Or we can get rid of our needs for circles and spinning by simplifying and move left and right. If we align our images in a row, and animate a translateX(-100%)
matching our 60 frames per second we get the same effect.
See the Pen Zoetrope Strip: 48 frames + 60fps by Dan Wilson (@danwilson) on CodePen.
\nsteps()
Timing Function? #You got me… we didn’t need to take this as far as we did to create images that flip through a bunch of frames, because we can do this by using a steps()
timing function in CSS (or easing
property if you are in the Web Animations API world). And now we don’t necessarily have to max out our speed to get a smooth frame by frame animation.
See the Pen Zoetrope Strip: Steps() by Dan Wilson (@danwilson) on CodePen.
\nSure. A friend of mine once had a spinning top with a specific arrangement of arcs on its surface, and once it is spinning the eye sees complete circles. I made the following demo to reproduce the effect (to an extent), but I soon found out this effect is even more involved and can cause different people to see different colors in the circles. This toy goes by the name of Benham’s Top.
\nSee the Pen Spinning Fast to Fill the Gaps by Dan Wilson (@danwilson) on CodePen.
\nAdding the solid black half circle introduces the color effects for some people. Interestingly, reversing the spin reverses where the colors appear.
\nMany people might see the limitation of today’s browsers and screens to animate at 60 frames per second as a problem. We always want more power, more speed, and more bandwidth working on the web, but sometimes the limitations give us new abilities and new ways to play.
\nAnd the limits of today are not necessarily the limitations of tomorrow.
\n", "date_published": "2018-08-13T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2018/05/offset-and-beyond/", "url": "https://danielcwilson.com/blog/2018/05/offset-and-beyond/", "title": "CSS Motion Path beyond the Big Three Properties", "content_html": "While still only in Chromium-based browsers and actively being drafted for its specification, CSS Motion Path has grown up a bit in its three years since Chrome 46 rolled out the first implementation.
\nI've previously documented the basics of the primary CSS Motion Path use case via the main three properties — offset-path
to define a path for an element to follow, offset-distance
to position the element on that path, and offset-rotate
to affect the direction the element faces.
There are additions to the spec since my last roundup (and even better, some features actually rolling out to Chrome), so assuming you are already familiar with the basics let’s dig in to the new functionality. To make sure you are seeing the latest, view this article and its demos in Chrome 66+ with Experimental Web Platform Features enabled in chrome://flags
.
See the Pen Banners in the Wind by Dan Wilson (@danwilson) on CodePen.
\nBy default, an element with a path will move as though the center of the element is connected to the path. We can change this location with the offset-anchor
property. This accepts a <position>
, so it takes values similar to background-position
or transform-origin
, with an x
and a y
, either as a numerical value or a keyword like center
.
See the Pen Offset Path Anchor by Dan Wilson (@danwilson) on CodePen.
\nA couple years ago, Chromium introduced support for the d
property in CSS that takes a path()
and maps directly to the value you would find in a d
attribute inside an SVG path
element. This allows you to change the definition of the path in CSS, and additionally supported animation if the browser can interpolate the values needed, as demonstrated by Chris Coyier.
See the Pen Simple Path Examples by Chris Coyier (@chriscoyier) on CodePen.
\nThe CSS Motion Path spec introduced similar interpolation logic for the offset-path
property. Therefore, not only can we move an element along a path, we can change the path along which it moves.
See the Pen Animating offset-distance & offset-path (& d) by Dan Wilson (@danwilson) on CodePen.
\nThe white path in this example is in the SVG, being animated in CSS with a keyframe animation modifying the d
property. The blue polygon is animating in two ways, starting with a more typical animation of its offset-distance
from 0%
to 100%
. It also is animating the offset-path
property itself, using the same two keyframes used in the SVG path’s d
animation.
The biggest change to the CSS Motion Path spec since its introduction was its combining with parts of thee CSS Round spec. In particular, the concept of polar coordinates was rolled into the offset-path
property. This will be a pretty powerful option as outlined in the spec, but its current "behind a flag" version is currently only a small subset of the feature. As such, I will only touch on what is implemented now, as the full version will be easier to explain when it is implemented.
The key here is that offset-path
now accepts a new ray()
function which specifies an angle. This angle creates a line to position through polar coordinates from the center of a circle. Combined with an offset-distance
, this angle will determine in which direction the element will move from the circle and how far.
.polar {
offset-path: ray(90deg);
offset-distance: 100px;
}
\nThis will take our element and move it 100px to the right. Using 135 degrees would move it to the bottom right.
\nSince we can now animate both offset-path
and offset-distance
we can animate an element going around a circle’s edge and animate it going toward and away from the center.
See the Pen CSS Motion Path with ray() by Dan Wilson (@danwilson) on CodePen.
\nThis is all in early stages. There will be a lot more to explore here once the full implementation lands. That is when we can talk about containing blocks, sides, and the fifth new CSS Motion Path property called offset-position
.
Houdini has been discussed here and there for a few years now, always with the promise that it will open doors we have never had. Want to make a new way to do layout? Houdini. Want to create a new kind of gradient? Houdini.
\nIn short, Houdini gives a developer building blocks in JavaScript to allow writing new kinds of CSS.
\nWe have reached the point where this is more than just an idea or a spec. There are modules now in Blink browsers behind a flag (as of this writing, this is the only way to try it). There are many code examples, but at first glance it all seems... complicated. Some of the key examples that resources point to look like a mysterious combination of CSS Variables, Canvas drawing, and JS in CSS.
\nLuckily, there is a way to start exploring Houdini with one basic JavaScript method, called CSS.registerProperty()
. We will look at Houdini in the context of CSS Custom Properties (CSS Variables) and animation.
See the Pen Dance of the Houdini Hexagons by Dan Wilson (@danwilson) on CodePen.
\nI saw the power of Custom Properties with their support independent transform properties in Firefox, Edge, Chrome, Opera, and Safari. They allows us, for example, to modify a scale
without modifying an existing rotate
on the same element, even though those are tied into a single transform
property.
Custom Properties are powerful since they introduced a variable option to CSS natively for the first time, and they fit in with the cascade and inheritance nicely. But they, by design for the first spec, are not as powerful as they can be. To get the initial implementations in so many browsers semi-quickly, their power was limited. When you say --my-prop: 2px
, the browser does not really know that is a length value. When it is used in the context of another property that expects a length (e.g. transform: translateX(var(--my-prop))
), the browser then is able to parse it and treat it like a length.
Therefore, you cannot set a transition
for --my-prop
or modify its value in a @keyframes
rule and have it animate smoothly. The browser does not have enough information to know what kind of syntax it is dealing with, or if it is even animatable. You can, however, still transition the property that it is used within if it is animatable (like the transform
earlier).
CSS.registerProperty()
from the Properties and Values API #With the Properties and Values API spec (inside the larger Houdini endeavor) we can now tell the browser what to expect for each property.
\nif (window.CSS && CSS.registerProperty) {
CSS.registerProperty({
name: '--my-prop',
syntax: '<length>',
initialValue: '0px',
inherits: false
});
}
\nHere we can say that we want our CSS custom property named --my-prop
to always have a valid <length>
value. We also specify an initial value (for when no value is defined when we reference it via var()
) and whether it inherits or not. To show the flexibility inherits
provides, take the following CSS:
ul, ol {
--my-prop: 20em;
}
li {
width: var(--my-prop);
}
\nIf inherits
is true
then the width of the list item will be 20em
since it can inherit its value from an ancestor. Otherwise, a false
value will mean the list items will have a width of 0px
since that was the initial value defined in our JavaScript and there is no other value defined specifically on the li
element.
By defining rules around how this property will behave we can have the browser reject syntaxes we do not want to support. Even more exciting (to me) is that we now have told the browser that a property is a length
syntax (or angle, number, color, etc. as defined in the spec) which the browser knows how to animate.
See the Pen Animating CSS Variables (Houdini Basics) by Dan Wilson (@danwilson) on CodePen.
\nWe register several properties in the JavaScript as angles, and then we give three nested elements each a transform
that is composed of the the same custom properties in different variations of rotations. We can give them all the exact same @keyframes
, but each will have a different animation since the custom properties combine into different transform
s.
This extends to transitions, as well where you can specify different transitions for each of the transform functions that make up a single transform
property.
But why stop there when you can even combine animations and transitions on a single transform
. In this example, each element still has a keyframe
animation, but on body:hover
the middle element will do an additional rotation around the Z axis, smoothly performing its transition
while the animation
continues. The browser has all the informaiton it needs to interpolate the values thanks to the custom property’s syntax defined with CSS.registerProperty()
.
See the Pen Animating CSS Variables (Houdini Basics 2) by Dan Wilson (@danwilson) on CodePen.
\nVincent De Oliveira has an excellent playground that shows off Houdini at a larger scale, including the other APIs under the Houdini banner like Paint and Layout. Google also is putting the initial Paint API in Chrome stable soon, with a solid introduction by Surma.
\nEnjoy.
\n", "date_published": "2018-02-12T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2018/02/optical-fun-barrier-grid/", "url": "https://danielcwilson.com/blog/2018/02/optical-fun-barrier-grid/", "title": "Barrier Grid Animation: Illusions on the Web Part 2", "content_html": "This is the second in an article series discussing different optical illusions & mechanical toys and how we can recreate them on the web (and improve them). Due to the nature of this method, some animations shown here will be fast and fill large screen space. All animations are paused by default.
\nIn various forms over the years, I have seen a technique to create animation in books or other non-digital and non-video forms. They go by many different names (Kinegrams, Scanimation®, stereographs), but for this discussion we will use the term “Barrier Grid Animation” as it describes the main shared component to create the illusion of motion.
\nSee the Pen Illusion of Motion Basic by Dan Wilson (@danwilson) on CodePen.
\nIn the non-digital world, this consists of a transparent sheet/cel with evenly spaced black lines on top of a second layer consisting of a seemingly chopped image. While not required, most of the time we are talking about vertical lines, so that is what we will assume.
\nSee the Pen Illusion of Motion Split by Dan Wilson (@danwilson) on CodePen.
\nWith just these two physical elements you can create several frames of animation. By only moving the topmost layer the underlying collection of seemingly random lines take shape and animate.
\nBoth layers follow a pattern based on the width of a bar. For simplifying the math, we will use 1px
as our base unit. Our first example had five frames of a pink ball moving around. For such an animation, our top layer (the barrier) should consist of a 1px
wide transparent bar followed by 4px
of a black bar, repeated.
The bottom layer (consisting of the animation's frames) will be 1px
wide of the first frame, followed by 1px
of the second frame, etc. This also is repeated, so when our barrier grid is overlaid the same frame will be visible through the small transparent bar, while the other four are covered. The following example demonstrates this (zoomed in for clarification) with each color representing a different frame.
See the Pen Illusion of Motion Zoom by Dan Wilson (@danwilson) on CodePen.
\nAs we move the top layer left and right we will see effectively one frame at a time in sequence. We are blocking out a majority of each image, but our brain will do its best to fill in the missing pieces as the animation progresses by moving the top layer.
\nWe want a pattern for our image where every fifth pixel is from frame 1, every fifth pixel plus one shows frame 2, etc. We will take the following markup where each unnamed div represents a frame (though the div.barrier
could also be implemented as a pseudoelement):
<main>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div class=\"cover\"></div>
</main>
\nThe cover boils down to a linear gradient to the right with 1px
transparency and 4px
black. I use CSS Variables to adapt it as I go, such as if I want to quickly see what happens if I change the base unit to 2px
.
.cover {
background:
linear-gradient(to right,
transparent 0px,
transparent var(--pixel), /* 1px */
var(--cover) var(--pixel),
var(--cover) calc(var(--pixel) * 5)); /* 5px = 1 + 4 */
background-size: calc(var(--pixel) * 5) 100%; /* repeat horizontally */
}
:root {
--pixel: 1px;
--cover: black;
}
\nThe hardest part of setting up the barrier grid animation is creating the base image with the chopped frames. However, with blend modes and repeating linear gradients we can accomplish it with CSS.
\nWe can take an image and prep it to be a frame in our animation by using multiple backgrounds — the topmost background being a repeating linear gradient on top of the image we want to chop.
\nSee the Pen Repeating Linear Gradient Over an Image by Dan Wilson (@danwilson) on CodePen.
\nTo get our second frame we apply the same gradient, but we adjust the background-position
. You can use a preprocessor to adjust this offset without repeating code, though we are only dealing with a few frames so the resulting CSS would be:
div {
background-position: 0px 0px;
}
div:nth-of-type(2) {
background-position-x: var(--pixel);
}
div:nth-of-type(3) {
background-position-x: calc(var(--pixel) * 2);
}
div:nth-of-type(4) {
background-position-x: calc(var(--pixel) * 3);
}
div:nth-of-type(5) {
background-position-x: calc(var(--pixel) * 4);
}
\nWe need to stack our frames over each other so we can position them all as absolute
, but we will only see the last frame at this point since we do not have anything with transparency on our frames. Luckily we chose white
as our gradient color which will pair nicely with a mix-blend-mode: multiply
setting.
See the Pen Repeating Linear Gradient Over an Image with Transparency by Dan Wilson (@danwilson) on CodePen.
\nA whole series could be written on blend modes, or even simply on multiplying colors. The quick-ish version is that for multiple overlapping elements, we look at each pixel and perform a function to blend the colors on each layer at that pixel. More specifically, the functions happen on each of the three RGB channel values, mapping to a range of 0
to 1
(so a value of 255
in a channel would equal 1
). With multiplication, whenever we have any color multiplied by black (with an R,G,B of 0,0,0
), the resulting color will always be black. This comes from standard mathematics where multiplying any number by zero will result in zero.
\nblack x red
\nrgb(0,0,0) x rgb(1,0,0)
\nrgb(0 x 1, 0 x 0, 0 x 0) = rgb(0,0,0);
\n
\nSimilarly, multiplying by white is like multiplying a number by one. Any number times one is equal to that same number, so any color multiplied by white will equal the same color.
\n\nwhite x red
\nrgb(1,1,1) x rgb(1,0,0)
\nrgb(1 x 1, 1 x 0, 1 x 0) = rgb(1,0,0);
\n
\nWith the math out of the way, we can see how applying the blend mode of multiply
can let the white bars of each frame effectively become transparent by letting anything that is not white be visible. Since each frame is offset by 1px
all frames are seen at once and our image is officially “chopped.”
See the Pen Illusion of Motion (How To Animation) by Dan Wilson (@danwilson) on CodePen.
\nWith the barrier constructed and the underlying animation images chopped, we are able to create our animation by moving our topmost layer horizontally. You can either do this with a fixed animation, tie into pointer events where you must drag the barrier layer, or tying it into the accelerometer to get that real old school magical vibe. I show these last two techniques in this full demo (for iOS or other devices that do not allow iframes to access accelerometer, you can view the direct demo)
\nSee the Pen Illusion of Motion by Dan Wilson (@danwilson) on CodePen.
\nNo.
\nOkay, fine. This technique grew out of putting animation into books and small toy packages. We have actual animation in CSS where the browser can fill in the frames for us, and we have the easing option steps()
(with future improvements coming) for cases where we want to specify frames more granularly.
But... this was at least fun, right?
\n", "date_published": "2018-02-07T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2018/02/rss-club/", "url": "https://danielcwilson.com/blog/2018/02/rss-club/", "title": "RSS Club for All", "content_html": "I've used RSS for years, but had shied away from it after the disappearance of iGoogle (that's right, I never relied on Google Reader, instead just the dashboard view Google had for a while) as I didn't have a good replacement. Well, really it was just I didn't know what a good replacement was.
\nBut people who know me know that I like reading and hyperclicking on the web. Big Social Media is more of a get in and get out type place with regard to links (or more likely skimming a headline, pressing a like or angry face, sharing it with a highly-likely-to-be-irrelevant opinion of the article you did not read, and scrolling to the next headline).
\nAs such, the days of RSS, blogrolls, and webrings still have a special place in my heart. While not perfect, I've wanted for a while to incorporate those ideas into my site (and side projects). At about the same time as I was refocusing on RSS and newsletters, I happened upon a post on Dave Rupert's RSS feed about a so called RSS Club. An idea to write posts specfically for those who subscribe to your RSS, and connect together unofficially through these feeds. He proposes three rules:
\nLove it.
\nI have a Jekyll setup similar to Dave, and since so he outlined his implementation details it was a low barrier for me to join. I'm making an additional change to my article templates (Dave still gives these special posts a URL, just only links to them through RSS). In an effort to make them a little more secretive, I'm going to minimize the searchability with a robots meta
tag in the head
:
{% raw %}
{% if page.rssonly %}
<meta name=\"robots\" content=\"none\">
{% endif %}
{% endraw %}
\nThis is the best practice way to do this now right? Instead of robots.txt
?
I have a few special posts that I might post here in advance for a week or two before releasing at large. I also have been sitting on a prototype for my sequel to Path to Palindromes for almost two years that I will probably talk about here to (maybe?) inspire me to finish it out. This might also be where I talk about how I'm adopting IndieWeb principles (and how that fits in with my Jekyll setup) and the hiccups I encounter. Not sure, but it's fun to have something new to try...
\n", "date_published": "2018-02-03T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2018/02/optical-fun-red-reveal/", "url": "https://danielcwilson.com/blog/2018/02/optical-fun-red-reveal/", "title": "The Red Reveal: Illusions on the Web Part 1", "content_html": "This is the first in an article series discussing different optical illusions & mechanical toys and how we can recreate them on the web (and improve them).
\nGrowing up, my family played a lot of board games. Several games such as Outburst, Password, and Clue Jr. included something that amazed me at the time — a red lens and cards with some light blue text that was obscured by a myriad of red lines. When you put the red lens over the card, the text would magically appear.
\nSee the Pen Drag to Make the Red Squiggles Disappear by Dan Wilson (@danwilson) on CodePen.
\nI spent twenty or so years without thinking about these again until I was watching an episode of Tumble Leaf where they discover hidden messages. This sent me on a whirlwind of finding related products and recreating the effect on the web, since most of my life is centered around recreating and extending things my kids like.
\nThis was harder to duckduckgo than I expected because I didn't know the concept's name. Some message boards on board game fan sites led me to the name "red reveal," and that is what I am using for this article.
\nThe basic effect is that you have an image or text on the base that is a cyan (let's say hsl(180,100%,50%)
). This will be the hidden content. We then add a mask that is red (180deg around the color wheel at hsl(0,100%,50%)
) over it, either with other text, red squiggles, a mix of lines, or other images. Then look at the card through a red lens (or cellophane — anything that is close to a true red and not opaque). Just like magic the cyan is now highly visible and the red mask is effectively removed.
We can recreate this on the web with three layers and use of blend modes, opacity, and repeating linear gradients, and custom properties.
\nOn our bottom layer, let's add text in cyan (or an image with cyan outlines). I also set an opacity between .25 and .5 so it's harder to see against the white background. We then add a second layer with multiple repeating linear gradients that create a stitched pattern that make it all the harder to see the cyan message.
\n.hide-the-message {
--reveal: hsl(0, 100%, 50%);
--start: 5px;
--end: 7px;
background:
repeating-linear-gradient(155deg,
transparent 0, transparent var(--start),
var(--reveal) var(--start), var(--reveal) var(--end)),
repeating-linear-gradient(115deg,
transparent 0, transparent var(--start),
var(--reveal) var(--start), var(--reveal) var(--end)),
repeating-linear-gradient(45deg,
transparent 0, transparent var(--start),
var(--reveal) var(--start), var(--reveal) var(--end));
}
\nIt's the third layer where we undo the obscuring lines and reveal the message. The lens is a circle with the same red as a background, with a multiply blend mode. The red lens now can effectively hide the red lines. The multiply blending makes the cyan combine with the red to create black (though, since an opacity was applied to the cyan it will not be fully black).
\nSee the Pen Multiply Mix Blend Example by Dan Wilson (@danwilson) on CodePen.
\nAdd JavaScript to make the element move as your mouse or finger moves with custom properties, and we have a complete interactive recreation.
\nvar lens = document.getElementById('lens');
function move(e) {
if (e.clientX || e.touches) {
var x = (e.clientX || e.touches[0].clientX);
var y = (e.clientY || e.touches[0].clientY);
lens.style.setProperty('--x', x +'px');
lens.style.setProperty('--y', y +'px');
}
}
\nSee the Pen Drag to Reveal the Image by Dan Wilson (@danwilson) on CodePen.
\nAs I learned more about this optical trick, I happened upon a Wide Eyed Editions book called Illuminature that takes it up a level by printing three images on top of each other in different colors. Then by using three different lenses (red, green, and blue) you are able to largely separate each out. The other two images become the mask for the image that matches the lens you choose.
\nIt is a beautiful book without the lenses, but it’s especially fun to explore and separate with the lenses. The provided lenses are small, and for such a large book, I felt it would be better to have a larger lens to look through.
\n\nI also had been meaning to learn about streaming video. So taking what I learned with having a red circle with a mix-blend-mode: multiply
to get those cyan markings to darken, I set up a red fullscreen element on top of a video
element. When you allow access to the camera (which prefers the camera that is facing away from a user when on a phone) it will show underneath the blending element. Toggle to green or blue to see the other images in the book with ease.
navigator.mediaDevices.getUserMedia({
audio: false,
video: { facingMode: 'environment' }
}).then(stream => {
var video = document.querySelector('video');
video.srcObject = stream;
video.onloadedmetadata = function(e) {
video.play();
};
}).catch(err => console.log('error', err));
\nYou can view the demo on CodePen (some platforms restrict camera access from iframes, like iOS, so you can also view the direct example to try it on those devices)
\nIt works fairly well, at least on par with the provided lenses.
\n", "date_published": "2018-02-01T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2018/01/year-2018/", "url": "https://danielcwilson.com/blog/2018/01/year-2018/", "title": "Personal Goals and Guidelines for Technology: 2018 Edition", "content_html": "I have written this post about twenty times over, but it keeps turning into a rant against notifications, social media, digital assistants, and Blue Bell ice cream.
\nSo instead of getting into the "why" (for now), I am going to simply document some goals I have for my site and guidelines for how I approach technology in general... in list form, since prose kept becoming a sixty paragraph ramble.
\nIn no particular order:
\nI might dive into some specifics of my guidelines in the future (a lot of people question me on my approach to notification and Facebook in particular), but for now the overall reason is I enjoy simplicity. I get no joy out of scrolling through Facebook, for example. I’d much rather subscribe to a couple newspapers and donate to public radio and television and focus on their content. But even then, on my own time and not just to kill time.
\n", "date_published": "2018-01-18T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2018/01/additive-animation/", "url": "https://danielcwilson.com/blog/2018/01/additive-animation/", "title": "Additive Animation with the Web Animations API", "content_html": "On CSS-Tricks: I discuss some of the newly implemented parts of the Web Animations API Level 1 spec that allow us to modify currently running animations. These are the pieces that will really distinguish the API from the CSS animations we know and love... coming soon to a browser near you.
\n\n", "date_published": "2018-01-02T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2017/10/all-the-transform-ways/", "url": "https://danielcwilson.com/blog/2017/10/all-the-transform-ways/", "title": "3 Ways to Use Independent Transform Properties", "content_html": "I figure there are three main ways we can break out the individual transform functions (such as translate
and scale
) from the transform
property without a library. The ability to set a new rotate
without it affecting a previously set scale
, for example, has long been desired by web developers. We’ll take a look at the pros and cons of each approach.
These pros and cons will be centered around each approach’s ability to support the following:
\nrotate
s, multiple scale
s, etc.)?scale
then translate
gives a different end result than translate
then scale
)?transition
)?keyframes
or the Web Animations API)?To see why the first two points matter, play around with the following pen by reordering the transform functions and adding more.
\nSee the Pen Visual Reference: Transform Coordinate Systems by Dan Wilson (@danwilson) on CodePen.
\nDefined in the in-progress CSS Transforms Level 2 specification (and in Chrome behind the Experimental Web Platform Features flag), translate
, scale
, and rotate
become their own properties. They can be handled individually, and therefore each can be animated and transitioned separately, too.
.independent-properties {
translate: 40px 10vmin; /* X Y */
rotate: 45deg;
scale: .9 .9; /* specify both X and Y */
}
.one-transform {
transform: translate(40px, 10vmin) rotate(45deg) scale(.9);
}
\nThe two rulesets have equivalent transformations. Now whenever we want to modify the transform — via classes or directly in JavaScript, for example — we can change only the desired property:
\n.independent-properties:hover {
rotate: 225deg;
}
.one-transform:hover {
transform: translate(40px, 10vmin) rotate(225deg) scale(.9);
}
\nYou can transition and animate each individually as well, so for example you can specify different easings and durations for your translate
than your rotate
.
Pros
\nCons
\ntransform
propertytranslate
, rotate
, scale
... if you need a different order you must go back to the transform
propertyTo be fair, the order that these individual properties are applied is a very common order for people to use, and multiple transformations are rarer, too. They still are a key difference with the transform
property which gives a lot more power.
I discovered the possibilities of mixing custom properties with the transform
property earlier this year, and well... I’m still excited about them. I’ll summarize the process here, but please check out the original article for more details.
The idea here is that we set up the standard transform
property as we always have with every transformation we want to eventually apply using custom properties (which are supported in Firefox, Edge, Safari, Chrome, and Opera).
.using-custom-properties {
--translate-x: 40px;
--translate-y: 10vmin;
--rotate: 45deg;
--scale: .9;
transform:
translateX(var(--translate-x, 0))
translateY(var(--translate-y, 0))
rotate(var(--rotate, 0deg))
scale(var(--scale, 1))
translateX(var(--end-translate-x, 0))
}
.using-custom-properties.changed {
--end-translate-x: 5px;
}
\nNow we can specify the order of our transformations, and we can do as many transformations as we want, since we are using our old friend transform
.
We can even do transitions on the transform
and any changes to one of the custom properties will trigger the transition. So in our example we could set a transition: transform 200ms ease-in-out
in our rule and when the changed
class is added we will smoothly move to our new location. The caveat here is that it is specifically a transition on the single transform
property so your translation, for example, cannot have a different duration, easing, or delay than your scale.
Here is an example showing this approach on a button with multiple states. It also shows the first approach if you are in an enabled browser so you can compare them.
\nSee the Pen Button Example: CSS Dev Conf by Dan Wilson (@danwilson) on CodePen.
\nPros
\nCons
\nAbout that last bullet point...
\nAs I discussed in an article about animating Single Div art, the Custom Properties Level 1 specification does not introduce mechanisms to specify a syntax or value type for a given custom property. Therefore, the following will either do nothing or flip states at the 50% mark in the animation:
\n.using-custom-properties {
/* using set up in last example */
animation: move-it 1000ms infinite alternate;
}
@keyframes move-it {
25% {
--translate-y: 20vmin;
}
50% {
--scale: 1;
}
100% {
--scale: .8;
--rotate: 90deg;
}
}
\nExcept that animation can in fact work... in the future! And you can sneak into the future if you have the Experimental Web Platform Features flag enabled in Chrome as it has some initial Houdini magic in it. If you are new to Houdini (and almost all of us are) it’s central idea is that developers can write some JavaScript to tell the browser how to render CSS it doesn’t know what to do with by default. I highly recommend enabling the aforementioned flag in Chrome (under chrome://flags
) and visiting this Houdini playground (and its references) to get an idea of how it works and what is possible.
One of the simpler things that Houdini provides is a way to tell the browser that a specific custom property follows a certain syntax. Now we can tell the browser via JavaScript to treat our --translate-x
custom property as a length
value and our --rotate
as an angle. With that small amount of extra information, the browser now has what it needs to do the keyframe animation as we would want.
See the Pen Houdini: Independent Transforms in Keyframes by Dan Wilson (@danwilson) on CodePen.
\nNow that you have a taste for the future and are okay with living in dev browsers and/or enabled flags, I want to talk about an old favorite of mine: the Web Animations API. This approach is not like the others and has a smaller use case, but it is still a way to work with transform functions independently.
\nThere are times you have multiple animations going on for the same element — maybe one is changing the opacity, while the other is moving it across the screen. They can have different durations, easings, etc. In the Web Animations API you can achieve this with the following:
\nelement.animate({
transform: ['translateX(0)', 'translateX(20px)']
}, 1500);
element.animate({
opacity: [0,1]
}, 1000);
\nLet’s change that to be two animations with two different transform
setups.
element.animate({
transform: ['translateX(0)', 'translateX(20px)']
}, 1000);
element.animate({
transform: ['rotate(0)', 'rotate(90deg)']
}, 1500);
\nSince those are created and running at the same time, only the rotation is applied because they are modifying the same property and the first animation’s requested changes are effectively ignored.
\nBut!
\nThe Web Animations specification introduces the composite
operation (and the related iterationComposite
). The default composite
is replace
and has the behavior we have had for years now where an animated property’s value simply replaces any previously set value — either from a rule set or another animation.
The add
value is where things change from the previous norms. Taking our example above with the two transform
animations, we can modify the second to use the new composite
value of add
.
element.animate({
transform: ['translateX(0)', 'translateX(20px)']
}, {
duration: 1000,
fill: 'both'
});
element.animate({
transform: ['rotate(0)', 'rotate(90deg)']
}, {
duration: 1500,
fill: 'both',
composite: 'add'
});
\nNow both animations will be seen as the browser on the fly figures out the appropriate transformation at a given point in the animation’s timeline accounting for both transformations. In our examples, the easing is 'linear'
by default, so we can break out what the effective transform
is at any given point. Such as:
translateX(0px) rotate(0deg)
translate(10px) rotate(30deg)
(halfway through first animation, 1/3 through second)translate(20px) rotate(60deg)
(end of first, 2/3 through second)translate(20px) rotate(90deg)
(end of second)The Web Animations API is in Firefox, Chrome, and Opera already. However, this piece of functionality is currently only in Firefox Nightly. You can see it in action in that browser (Nightly is v58 as of this writing) in the following samples. If you view them in another browser you will see the typical replace
behavior instead.
See the Pen Animation Composite Add (Firefox Nightly) by Dan Wilson (@danwilson) on CodePen.
\nSee the Pen Animation Composite Tests (WAAPI) by Dan Wilson (@danwilson) on CodePen.
\nThere is also an ongoing discussion in the CSS Working Group about adding this functionality to CSS, even beyond the context of animations.
\nPros
\ncomposite: 'add'
optionCons
\nThere surely are other ways to accomplish this (such as doing a lot of calculations in a requestAnimationFrame
or specifying all the different combinations possible in CSS), but these three represent the solutions with the least overhead and have the future in mind. It bears repeating that not everything discussed works today and none of these are in every browser that is available.
It doesn’t really bear repeating, though; that’s just how the web works. There is nothing wrong with looking to the future, and seeing how emerging technology is solving the problems of today.
\n", "date_published": "2017-10-31T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2017/07/animating-single-div-art/", "url": "https://danielcwilson.com/blog/2017/07/animating-single-div-art/", "title": "Animating Single Div Art", "content_html": "This article originally appeared on CSS Tricks on May 31, 2017.
\nWhen you dig deep with your tools, it is amazing what you can create out of the most basic of HTML. I have been constantly impressed with “Single Div Art” by Lynn Fisher and others where you take a single generic <div>
to create a beautiful cactus, Alamo, or panda.
See the Pen #dailycssimages 01: Bear Cub by Lynn Fisher (@lynnandtonic) on CodePen.
\nIt can be hard to jump in and deconstruct how these are made, due to the fact they are using layers of background gradients, box shadows, text shadows, and more in the midst of just a single div and its ::before
and ::after
pseudo elements. Before we go any further… check out Lynn Fisher’s article on why and how she started working with single div art.
One thing that single div
pieces rarely do is animate. If you can transform your div
or one of its pseudo elements, that’s fair (as Lynn Fisher does with her fantastic BB-8 [div](http://a.singlediv.com/#bb8)
). But you cannot directly change the opacity
or transform
of the individual “elements” you create inside your div
, since they are not actual DOM elements.
I am a big believer of trying something a little different and interesting to learn tools you otherwise might never learn. Working with the constraints of a single div
might not be great for production work, but it can be a great exercise (and challenge) to stretch your skills in a fun way. In that spirit, we’ll use this technique to explore how Custom Properties (CSS Variables) work and even provide us a path to animation inside our div
. To illustrate along the way we will be breaking down the following example with multiple animation approaches:
See the Pen Single Div Accordion (Animated with CSS Variables) by Dan Wilson (@danwilson) on CodePen.
\nThis accordion (the instrument, not the UI construct) has three main parts, the keyboard side (our div
), the bellows (the part that squeezes, which is the div::before
), and the button side (div::after
). Since the accordion naturally divides into these pieces, we can transform
each piece inside CSS Keyframe animations to get our animation started. The bellows are going between different scaleX
values and the two sides are using countering translateX
values to move with the scaling bellows. Thus, the squeezebox is born.
See the Pen Single Div Accordion Breakdown: Transforms by Dan Wilson (@danwilson) on CodePen.
\n<div>
with CSS Custom Properties #Animating and thinking about the three big pieces is more straightforward than thinking about what appears inside. It can be helpful to group and name the individual bits inside the div
, and Custom Properties provide us a native way to do this. Instead of levels of seemingly infinite linear gradient stops, you can define the -white-key
s and --black-key
s for a piano keyboard. Instead of a cross-section of multiple layered gradients you can have a --tea-cup
with its individual --tea-bag
and a related --tea-bag-position
defined inside.
The left side of the accordion boils down to:
\nbackground:
var(--shine),
var(--shine),
var(--button-key1, var(--button)),
var(--button-key2, var(--button)),
var(--button-key3, var(--button)),
var(--black-keys),
var(--white-keys),
var(--keyboard-base);
\nThose variable values might be several lines long (even hundreds), but conceptually how the layers of the keyboard come into play are clearer thanks to the variables.
\nSee the Pen Single Div Accordion Breakdown: Keyboard by Dan Wilson (@danwilson) on CodePen.
\nWhile the same can be done with Sass or Less, Custom Properties allow us to modify these values in the future. We can now conceptually think about animating just our --button-key2
or the accordion’s decorative --shine
. There are a few ways to tackle this.
The first way is to use CSS keyframe animations to change the property that contains the piece you want to move. If you want to change something inside your background (say, for example, we want to change the color of our “shine” lines from red to blue), you can set swap out values in the background
property. Building on the previous code sample:
div {
/* using background definition from earlier */
--shine: linear-gradient(to right, transparent 29.5%, red 29.5%, red 70.5%, transparent 70.5%);
--shine-blue: linear-gradient(to right, transparent 29.5%, blue 29.5%, blue 70.5%, transparent 70.5%);
animation: modify-shine 2000ms infinite alternate ease-in-out;
}
@keyframes modify-shine {
100% {
background:
var(--shine-blue), /*these two replace the original --shine*/
var(--shine-blue),
/* the rest of the background remains unchanged */
var(--button-key1, var(--button)),
var(--button-key2, var(--button)),
var(--button-key3, var(--button)),
var(--black-keys),
var(--white-keys),
var(--keyboard-base);
}
}
\nThis give us a lot, especially since background
is animatable (as are text-shadow
and box-shadow
). In this example there would be a transition from red to blue.
If your property is long, this can be hard to maintain, though Custom Properties can give us a help by extracting out the parts that don’t change to minimize the repetition. We can take it further by abstracting out pieces that don’t need to animate into a new variable - resulting in levels of variables:
\ndiv {
--static-component:
var(--button-key1, var(--button)),
var(--button-key2, var(--button)),
var(--button-key3, var(--button)),
var(--black-keys),
var(--white-keys),
var(--keyboard-base);
background:
var(--shine),
var(--shine),
var(--static-component);
}
@keyframes modify-shine {
100% {
background:
var(--shine-blue),
var(--shine-blue),
var(--static-component);
}
}
\nThe three musical notes in the accordion are based on this approach by animating text-shadow
.
See the Pen Single Div Accordion Breakdown: Music Notes by Dan Wilson (@danwilson) on CodePen.
\nA related way to change states is to directly change the custom property inside the keyframes
.
@keyframes {
0% {
--button1-color: var(--color-primary);
}
100% {
--button1-color: var(--color-secondary);
}
}
\nA custom property has no predefined behavior and is not a useful property until it is used with var(…)
, so the spec states changing one’s value will cause it to flip its value at 50%. This is the default behavior for all CSS properties that are not animatable and means it will not transition between the values.
You may have guessed since I already mentioned the spec that this is not available in all browsers. Currently, this is supported in Chrome and Opera only.
\nThis will be a quick way to get jump states when it is supported across browsers. If you are viewing this in Chrome or Opera, the accordion uses this approach to animate the keys on the keyboard and the buttons on the right side. For a smaller example, here is a “Pixel Art” example using this approach where the eyes and eyebrows will move in Blink browsers. Other browsers will nicely fall back to a static image. This is in many ways will use the least amount of code, but will have the least amount of support.
\nSee the Pen Pixel Art Animated with Custom Properties by Dan Wilson (@danwilson) on CodePen.
\nA third method is to use JavaScript to set new property values directly (or apply classes that have different property values). In its basic form this could be a call to setInterval
to toggle an on/off state for a value (for our piano this could be a key pressed or not).
var div = document.querySelector('div');
var active = false;
setInterval(function() {
active = !active;
div.style.setProperty('--white-key-1',
'var(--white-key-color-' + (active ? 'active' : 'default') +')')
}, 1000);
\nWith the corresponding CSS:
\ndiv {
--white-key-1: var(--white-key-color-default);
--white-key-color-default: #fff;
--white-key-color-active: #ddd;
/* And a linear gradient that follows the following pattern */
background: linear-gradient(to right, var(-white-key-1) 5%, var(--white-key-2) 5%, var(--white-key-2) 10%, ...);
}
\nSee the Pen Single Div Piano Keys by Dan Wilson (@danwilson) on CodePen.
\nWe are using JavaScript to set the white-key-1
to be either the value from the variable white-key-color-default
or white-key-color-active
depending on its state.
This method is useful when toggling something on and off (such as with a direct change in size, position, or color). This is how the buttons on the right side of the accordion are animated (as a fallback when the Keyframe approach is not supported).
\nSee the Pen Single Div Accordion Breakdown: Right by Dan Wilson (@danwilson) on CodePen.
\nEach of the nine buttons has CSS uses the following default circle, where --color1
is a light blue and --button-dim
is 1.4vmin:
--button: radial-gradient(circle,
var(--color1) var(--button-dim),
transparent var(--button-dim));
\nIf i want to change a specific button later to a “pressed” state I can set up a specific value in the CSS, for example the fourth button:
\n--button4: radial-gradient(circle,
var(--button4-color, var(--color1)) var(--button4-dim, var(--button-dim)),
transparent var(--button4-dim, var(--button-dim)));
\nThis property is similar, but it replaces the --button-dim
and --color1
with values that are specific to this button combined with a default value inside the var()
. This default value can be specified in our variables by using the form var(--my-specific-variable, 13px)
. We can take it a little further and even use another variable value as our default, e.g. var(--my-specific-variable, var(--my-default-variable))
. This second form is what our previous code example uses to create a specific definition for our fourth button while keeping its default value the same. If you have buttons you want to remain unchanged, they can use the default --button
property in a different background-position
.
In the accordion example, --button4-color
or --button4-dim
are never explicitly defined in the CSS. So when loaded they use the default values of --color1
and --button-dim
. The JS ultimately modifies the values and creates our on/off animation.
var enabled = false;
setInterval(function() {
enabled = !enabled;
div.style.setProperty('--button4-dim', enabled ? '1.2vmin' : 'var(--button-dim)');
div.style.setProperty('--button4-color', enabled ? 'var(--color1alt)' : 'var(--color1)');
}, 1000);
\nThis will give us behavior similar to changing the Custom Properties directly in a keyframe where values jump from state to state with no transition. As we’ve already discussed, background
and the *-shadow
properties are animatable (and transitionable… not in a high performance transform
or opacity
kind of way… but in small uses that can be okay).
If we take our current JS on/off approach and combine it with a CSS transition
on the background
, we can get a transition instead of a jump state.
div {
transition: background 2000ms ease-in-out;
}
\nDepending on how your individual components are composed, the ability to transition the property may not be possible. If you want to move something, you might need to look to requestAnimationFrame
.
One of my favorite Single Divs out there is a backpack by Tricia Katz:
\nSee the Pen Single Div Backpack by Trish Katz (@techxastrish) on CodePen.
\nI would love for that zipper to move back and forth. With a single custom property to represent the zipper’s x
position we can reach for requestAnimationFrame
to change that value and move the zipper right and left.
See the Pen Single Div Backpack with CSS Variables for Animation by Dan Wilson (@danwilson) on CodePen.
\nThere are several approaches to animating inside a div
that can stretch your skills. To get the broadest support we can’t rely on CSS alone right now, though we can still get pretty far. Custom Properties make modifying values more direct, even when we need to combine with JavaScript (and we can lean on our variable naming to be clear what we are changing).
See the Pen Single Div Animation Options by Dan Wilson (@danwilson) on CodePen.
\nWhenever you want to learn a new thing in CSS or JavaScript… see if you can find a “Single Div”-esque way to learn it. When you need to conceptually break properties apart or animate complicated values, Custom Properties can bring something new to the table.
\n", "date_published": "2017-07-13T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2017/06/scrubbing/", "url": "https://danielcwilson.com/blog/2017/06/scrubbing/", "title": "Scrubbing via the Web Animations API", "content_html": "A question appeared on a great Web Animations API article on CSS Tricks about whether or not the API would ever support scrubbing through an animation. As is often the case it does support that, but at a lower level (meaning you can't just say animation.showControlsPlease()
).
See the Pen Scrub Multiple Animations in WAAPI by Dan Wilson (@danwilson) on CodePen.
\ncurrentTime
#Creating a player control like the scrubber is possible thanks to the API's currentTime
property which is a read/write value that shows (or sets) what millisecond the animation is at. An animation with only one iteration will have a currentTime
with a max value of the duration
plus any delays.
Creating a scrubber for a single animation then becomes an exercise in currentTime
management. Things get more interesting as we want to scrub through multiple animations as a group, which is where we will focus (though what is covered here can be simplified to handle scrubbing one animation).
Our example focuses on five separate elements with one animation each (a transform
that moves each element to the right with a rotation) that all have the same duration. Each additionally has a delay
to stagger the animations. Since currentTime
includes delays, as the delay
gets bigger, so does the max currentTime
value.
We add one useful property to the animations to counter the delay
: an endDelay
. This does not currently have a CSS animation counterpart, but effectively does what delay
does... just at the end. This is particularly important when thinking about currentTime
. If I can set up all my animations to start playing at the same time and stop playing at the same (again, both delays will be used to caluclate the max currentTime
value), then I can use a single control/scrubber to affect all the animations at once.
var divs = Array.from(document.querySelectorAll('div'));
var animations = [];
const DURATION = 8000;
divs.forEach((div, i) => {
var anim = div.animate({
transform: ['translateX(0) rotate(0deg)', 'translateX(80vw) rotate(2700deg)']
}, {
duration: DURATION,
easing: 'ease-in-out',
fill: 'both',
delay: DURATION / 4 * i,
endDelay: DURATION - (DURATION / 4 * i)
});
animations.push(anim);
});
\nNow all the animations will kick off at effectively the same time (since I am calling animate()
all together). The currentTime
will start counting up for each, even though they don't all start moving at once since currentTime
includes delay
and endDelay
. Each has a duration of 8000ms, and each also has a delay/endDelay combination that adds up to 8000ms. Therefore, the five animations all will have a currrentTime
that maxes out at 16000ms.
I'm using a simple unstyled HTML range input in this example to act as the scrubber with a min value of 0
and a max value of 16000
to match our min/max currentTime
.
We need three things now:
\ncurrentTime
currentTime
When the animation is playing we can use requestAnimationFrame
to move the range slider according to the currentTime
of the animations.
var isPlaying = true;
var scrub = document.getElementById('scrub');
adjustScrubber();
function adjustScrubber() {
if (isPlaying) {
scrub.value = animations[0].currentTime;
requestAnimationFrame(adjustScrubber);
}
}
\nWhen the user is scrubbing through the full animation we can pause our individual animations and set their currentTime
s based on the range value:
scrub.addEventListener('input', e => {
var time = e.currentTarget.value; //The range's value will be: 0 <= val <= 16000
animations.forEach(animation => {
animation.currentTime = time;
});
pauseAll();
});
function pauseAll() {
isPlaying = false;
animations.forEach(animation => {
animation.pause();
});
}
\nFinally we can play everything again when we stop dragging and release the mouse/touch:
\nscrub.addEventListener('change', e => {
if (animations[0].currentTime >= e.currentTarget.getAttribute('max')) {
finishAll();
return false;
}
requestAnimationFrame(adjustScrubber);
playAll();
});
function playAll() {
isPlaying = true;
animations.forEach(animation => {
animation.play();
});
}
function finishAll() {
isPlaying = false;
animations.forEach(animation => {
animation.finish();
});
}
\nThis has an additional check for when we stop dragging at the max value (16000). In that case we actually don't want it to play again, but we do want it to "finish" the animations and get them to their full end states. To keep requestAnimationFrame
from firing forever once finished, we add an additional onfinish
callback to one of the animations that toggle the isPlaying
flag to false.
animations[0].onfinish = function() {
isPlaying = false;
console.log('finished');
}
\nOne limitation of the example to note: The way I've structured the event listeners (a combination of input
and change
events) means it will work with touch/mouse... but not the arrow keys on a keyboard. I will be investigating better approaches for that to make this more accessible.
This scrubbing process requires a certain setup where all the animations start at the same time and have the same length. Going beyond this is possible with the current tools, but it requires a bit more thought and customization. Similar processes could be used to adjust the timeline based on scroll or other inputs.
\nWe will have more options as the Web Animations API expands. The second level of the spec is exploring grouping and sequencing of animations such that there are multiple animations going along a shared timeline. I love that we can already accomplish a lot though with the default individual timelines and currentTime
property.
This article originally appeared on CSS Tricks on May 10, 2017.
\nCSS Custom Properties (CSS Variables) provide us ways to make code more concise, as well as introduce new ways to work with CSS that were not possible before. They can do what preprocessor variables can… but also a lot more. Whether you have been a fan of the declarative nature of CSS or prefer to handle most of your style logic in JavaScript, Custom Properties bring something to the table for everyone.
\nMost of the power comes from two unique abilities of Custom Properties:
\nEven more power is exposed as you combine Custom Properties with other preexisting CSS concepts, like calc()
.
You can use Custom Properties to do effectively what variables in preprocessors like Sass provide - set a global or scoped variable value, and then use it later in your code. But thanks to the cascade, you can give new property values inside a more specific rule.
\nThis cascading can lead to several interesting approaches, as shown by Violet Peña with an overview of the key benefits of variables and Chris with a roundup of site theming options.
\nPeople have been discussing these benefits from the cascade for a few years now, but it often gets lost in the conversation despite being a key functionality that differentiates it from preprocessors. Amelia Bellamy-Royds discussed it in the context of SVG and use
in 2014, Philip Walton noted a lot of these general cascading benefits in 2015, and last year Gregor Adams showed how they can be used in a minimal grid framework. Taking advantage of the cascade is likely the easiest way to start working with Custom Properties with progressive enhancement in mind.
Okay. Now that we know Custom Properties can natively give us some functionality preprocessors have and some new uses thanks to the cascade - do they give us anything we simply never could do before?
\nYou bet!
\nAll of the properties that have multiple parts are able to be used differently now. Multiple background
s can be separated, and multiple transition-duration
s can be broken out individually. Instead of taking a rule like transform: translateX(10vmin) rotate(90deg) scale(.8) translateY(5vmin)
you can set one rule with several custom properties and change the values independently thereafter.
.view {
transform:
translateX(var(--tx, 0))
rotate(var(--deg, 0))
scale(var(--scale, 1))
translateY(var(--ty, 0));
}
.view.activated {
--tx: 10vmin;
--deg: 90deg;
}
.view.minimize {
--scale: .8;
}
.view.priority {
--ty: 10vmin;
}
\nIt takes a bit to initialize, but then that little bit of extra effort up front sets you up to modify each transform function independently based on the needs of the class/selector rule. Your markup can then include any or all of the classes defined on each .view
element and the transform
will update appropriately, as the demo based on this code sample shows.
While independent transform properties are coming (and at that time translate
, scale
, and rotate
will be first level citizens), they are currently only in Chrome behind a flag. With Custom Properties you can get this functionality today with more support (and the additional ability to define your own order of functions, since rotate(90deg) translateX(10vmin)
is different than translateX(10vmin) rotate(90deg)
, for example).
If you are okay with them sharing the same timing options, they can even animate smoothly when using transition
when changing any of the variables. It's kind of magical.
See the Pen CSS Variables + Transform = Individual Properties (with Inputs) by Dan Wilson (@danwilson) on CodePen.
\nYou can build on these concepts when combining with calc()
. Instead of always setting variables as above with units (--card-width: 10vmin
or --rotation-amount: 1turn
) you can drop the units and use them in more places with a relation to one another. Now the values in our Custom Properties can be more dynamic than they already have been.
While calc()
has been around for a few years now, it has arguably been most useful when trying to get a result from adding values with different units. For example, you have a fluid width
in percentage units that needs to be shortened by 50px (width: calc(100% - 50px)
). However, calc()
is capable of more.
Other operations like multiplication are allowed inside calc
to adjust a value. The following is valid and gives us a sense that the transforms and filters are related to one another since they all use the number 10.
.colorful {
transform:
translateX(calc(10 * 1vw))
translateY(calc(10 * 1vh));
filter: hue-rotate(calc(10 * 4.5deg));
}
\nThis likely isn’t as common a use case because it is a calculation you don’t need the browser to compute. 10 * 1vw
will always be 10vw
so the calc gives us nothing. It can be useful when using a preprocessor with loops, but that is a smaller use case and can typically be done without needing CSS calc``()
.
But what if we replace that repeated 10
with a variable? You can base values from a single value in multiple places, even with different units as well as open it up to change values in the future. The following is valid thanks to unitless variables and calc
:
.colorful {
--translation: 10;
transform:
translateX(calc(var(--translation) * 1vw))
translateY(calc(var(--translation) * 1vh));
filter: hue-rotate(calc(var(--translation) * 4.5deg));
will-change: transform, filter;
transition: transform 5000ms ease-in-out, filter 5000ms linear;
}
.colorful.go {
--translation: 80;
}
\nSee the Pen Single Custom Property, Multiple Calcs by Dan Wilson (@danwilson) on CodePen.
\nThe single value can be taken (initially 10, or later changed to 80... or any other number) and applied separately to vw
units or vh
units for a translation. You can convert it to deg
for a rotation or a filter: hue-rotate()
.
You don’t have to drop the units on the variable, but as long as you have them in your calc
you can, and it opens up the option to use it in more ways elsewhere. Animation choreography to offset durations and delays can be accomplished by modifying the base value in different rules. In this example we always want ms
as our end unit, but the key result we want is for our delay
to always be half the animation’s duration
. We then can do this by modifying only our --duration-base
.
See the Pen Delay based on Duration by Dan Wilson (@danwilson) on CodePen.
\nEven cubic beziers are up for Custom Properties modification. In the following example, there are several stacked boxes. Each one has a slightly smaller scale, and each is given a cubic bezier multiplier. This multiplier will be applied individually to the four parts of a baseline cubic-bezier. This allows each box to have a cubic bezier that is different but in relation to one another. Try removing or adding boxes to see how they play with one another. Press anywhere to translate the boxes to that point.
\nSee the Pen Spiral Trail... Kinda by Dan Wilson (@danwilson) on CodePen.
\nJavaScript is used to randomize the baseline on each press, as well as setting up each box’s multiplier. The key part of the CSS, however, is:
\n.x {
transform: translateX(calc(var(--x) * 1px));
/* baseline value, updated via JS on press */
transition-timing-function:
cubic-bezier(
var(--cubic1-1),
var(--cubic1-2),
var(--cubic1-3),
var(--cubic1-4));
}
.advanced-calc .x {
transition-timing-function:
cubic-bezier(
calc(var(--cubic1-1) * var(--cubic1-change)),
calc(var(--cubic1-2) * var(--cubic1-change)),
calc(var(--cubic1-3) * var(--cubic1-change)),
calc(var(--cubic1-4) * var(--cubic1-change)));
}
\nIf you are viewing this in certain browsers (or are wondering why this example has an .advanced-calc
class) you might already suspect there is an issue with this approach. There is indeed an important caveat... calc
magic does not always work as expected across the browsers. Ana Tudor has long discussed the differences in browser support for calc
, and I have an additional test for some other simplified calc
use cases.
The good news: All the browsers that support Custom Properties also largely work with calc
when converting to units like px
, vmin
, rem
, and other linear distance units inside properties such as width
and transform: translate()
.
The not-so-good news: Firefox and Edge often have problems with other unit types, such as deg
, ms
, and even %
in some contexts. So the previous filter: hue-rotate()
and --rotation
properties would be ignored. They even have problems understanding calc(1 * 1)
in certain cases so even remaining unitless (such as inside rgb()
) can be a problem.
While all the browsers that support Custom Properties will allow variables inside our cubic-bezier
, not all of them allow calc
at any level. I feel these calc
issues are the main limiting factors with Custom Properties today… and they’re not even a part of Custom Properties.
There are bugs tracked in the browsers for these issues, and you can work around them with progressive enhancement. The earlier demos only do the cubic-bezier
modifications if it knows it can handle them, otherwise you get the baseline values. They will erroneously pass a CSS @supports
check, so a JS Modernizr-style check is needed:
function isAdvancedCalcSupported() {
document.body.style.transitionTimingFunction = 'cubic-bezier(calc(1 \\* 1),1,1,1)';
return getComputedStyle(document.body).transitionTimingFunction != 'ease';
//if the browser does not understand it, the computed value will be the default value (in this case \"ease\")
}
\nCustom Properties are great for what they provide in CSS, but more power is unlocked when you communicate via JavaScript. As shown in the cubic-bezier
demo, we can write a new property value in JavaScript:
var element = document.documentElement;
element.style.setProperty('--name', value);
\nThis will set a new value for a globally defined property (in CSS defined in the :root
rule). Or you can go more direct and set a new value for a specific element (and thus give it the highest specificity for that element, and leave the variable unchanged for other elements that use it). This is useful when you are managing state and need to modify a style based on given values.
David Khourshid has discussed powerful ways to interact with Custom Properties via JS in the context of Observables and they really fit together nicely. Whether you want to use Observables, React state changes, tried-and-true event listeners, or some other way to derive value changes, a wide door is now open to communicate between the two.
\nThis communication is especially important for the CSS properties that take multiple values. We've long had the style
object to modify styles from JavaScript, but that can get complicated as soon as we need to modify only one part of a long value. If we need to change one background out of ten that are defined in a background
rule, we have to know which one we are modifying and then make sure we leave the other nine alone. This gets even more complicated for transform
rules when you are trying to only modify a rotate()
and keep the current scale()
unchanged. With Custom Properties you can use JavaScript to modify each individually, simplifying the state management of the full transform
property.
See the Pen Dance of the Hexagons and Variables by Dan Wilson (@danwilson) on CodePen.
\nThe unitless approach works well here, too. Your setProperty()
calls can pass raw numbers to CSS instead of having to append units, which can simplify your JavaScript in some cases.
As Custom Properties are now in the latest browsers from Mozilla, Google, Opera, Apple, and Microsoft - it's definitely a good time to explore and experiment. A lot of what is discussed here can be used now with sensible fallbacks in place. The calc
updates needed in some of the browsers are further out, but there are still times when you can reasonably use them. For example, if you work on hybrid mobile apps that are limited to more recent iOS, Android, or Windows versions you will have more room to play.
Custom Properties present a big addition to CSS, and it can take some time to wrap your head around how it all works. Dip your toes in, and then dive in if it suits you.
\n", "date_published": "2017-06-07T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2017/04/individualized-properties/", "url": "https://danielcwilson.com/blog/2017/04/individualized-properties/", "title": "Individualizing CSS Properties with CSS Variables", "content_html": "In "A Trick: Individual CSS Transform Functions", I discussed how we could use CSS Variables (Custom Properties) to bring us close to independent transform properties. Each day I explore CSS Variables, however, I'm finding they can help us with any property that accepts multiple values at the same time.
\nSee the Pen Pause Independent Animations with CSS Variables by Dan Wilson (@danwilson) on CodePen.
\nThis example shows a block of text that animates via clip-path
to reveal itself over another block of text. I've added a second keyframe animation to do a hue-rotate
filter
to effectively cycle through colors for the background. The animation
property therefore has two values: one for each animation. If I want to change anything later for only one animation - such as a duration
or play-state
, I would likely take one of two approaches (Let's talk about toggling the animation-play-state
from paused
to running
):
running,running
; running,paused
; paused,running
; paused,paused
;), and use JavaScript to determine the correct one and toggle the class. (A fork of this example on CodePen by Shaw shows how you can take this approach and still make this with a small amount of code via the checkbox hack)getComputedStyle
on our animating element, tokenize the value based on the comma, set the desired value on the one we want to update, and then join back together for a final result string to be set on the element's style.animationPlayState
.These approaches have drawbacks, since they both grow more complex as we add more animations. We need nine classes if we have three animations and more complicated JS to get the right class applied. For the second option, we need to add additional logic to our JS any time a new animation is added, and we need to be careful to not change order in our CSS.
\nInstead, our example shows how we can use CSS Variables to break them apart and think of them more individually. We set up our animation
as we normally would but we also set a separate animation-play-state
property value, with the default values of running:
main {
--shape-play-state: running;
--hue-play-state: running;
animation:
reveal-shape 3000ms 1000ms infinite alternate ease-in-out both,
hue-adjust 6000ms 0ms infinite alternate ease-in-out;
animation-play-state:
var(--shape-play-state),
var(--hue-play-state);
}
\nNow we can add some form controls that allow us to pause an individual animation via a small amount of JS:
\nvar main = document.querySelector('main');
//Get Form controls with an id that syncs with the CSS Variable names
document.getElementById('shape').addEventListener('click', updatePlayState);
document.getElementById('hue').addEventListener('click', updatePlayState);
function updatePlayState(e) {
var variable = '--' + e.currentTarget.id + '-play-state';
main.style.setProperty(variable, e.currentTarget.checked ? 'paused' : 'running')
}
\nThis allows us to break out of manipulating full property values when we only need to update a small segment. We could take this further and add controls to everything... for each animation's durations, direction, iteration count, etc. It's not just CSS Animations and Transforms that take multiple values/functions in their properties and benefit from this approach.
\nWe can use CSS Variables to switch Hue, Saturation, and Lightness for color: hsl()
:
See the Pen HSL by Dan Wilson (@danwilson) on CodePen.
\nWe can combine it with "The Original CSS Variable" currentColor
and theme a site:
See the Pen Site Theming with input[type=range] by Dan Wilson (@danwilson) on CodePen.
\nWe can even update the individual parts of a cubic-bezier()
easing function on the fly:
See the Pen Jump to Where You Press by Dan Wilson (@danwilson) on CodePen.
\nMultiple backgrounds, box shadows, and more are open to this simplification. I've focused on using JS to manipulate values individually, but that certainly is not necessary. With backgrounds, for example, you could have the following instead of repeating all the parts for each variation (See the Demo):
\n.card {
--front: radial-gradient(circle, teal 20%, teal 40%, transparent 40%);
--mid: linear-gradient(135deg, transparent 20%, cyan 20%, cyan 50%, transparent 40%);
--back: linear-gradient(45deg, cyan 40%, hotpink 40%, hotpink 60%);
background: var(--front), var(--mid), var(--back);
}
.card.highlight {
--front: radial-gradient(circle, yellow 20%, yellow 60%, transparent 60%);
}
.card.subdued {
--mid: linear-gradient(transparent, transparent);
}
\nIf you understand and like the CSS cascade, CSS Variables will likely fit into your workflow well.
\nThe latest Chrome, Safari, and Firefox do well with these examples (and as a side bonus: Firefox is only one version away from supporting clip-path
without a flag). Edge 15, which was just released, is the first version to have CSS Variables, so I'm just starting to play with them there and... they largely work. Impressive for an initial release in my book, and the more common use cases for Variables seem to be working very well. The transition/animation ones do not always match the other browsers. Hopefully we will see improvements in all the browsers as we continue to explore what CSS Variables can do.
We get a lot of power through the single transform
property in CSS - allowing us to rotate, translate, scale, and more all at once. But allowing all of those different transform functions into one property can trip us up.
It's common to want to apply different transforms to different states of our elements. Say we have a button that we always want to be translated -150%
vertically. When a user hovers over the button we scale the button down a bit, and on press (the active state) we rotate it 180 degrees. This example shows how one might first think to write the CSS for "My Button" as just described, and then the "Expected" button shows a way to get it as intended.
See the Pen Basics with Transform Functions by Dan Wilson (@danwilson) on CodePen.
\nWith the initial styles on the button we are not just adding the scale on hover... we are overriding the original translate as well, so it scales and transitions back to translateY(0)
.
Why is this? Linear Algebra. The way these transformations happen depend on each other and their order (such that translate(-50%, -50%) scale(.4) rotate(50deg)
is different than rotate(50deg) translate(-50%, -50%) scale(.4)
), and they boil down to matrix multiplication. But we usually don't need to know transforms at that level. Web developers just want to know how they can maintain these transform functions at an individual level.
See the Pen Order of Transform Functions by Dan Wilson (@danwilson) on CodePen.
\nChrome has begun implementing individual properties, such that translate
, rotate
, and scale
become first class properties as seen in this previous example (as of this writing requires Chrome Canary). But this has limitations of its own:
x
, y
, and z
pieces of each are still tied to a single property currently.translate scale rotate
when it is converted to matrices.Use CSS Variables.
\nListening to David Khourshid talk about CSS Variables quickly opened my eyes to a lot of opportunities for animating with them. It wasn't until I started putting variables everywhere that their power became even clearer. Without further ado... here is the trick to give us more flexibility (with the how/what/why after the example).
\nSee the Pen CSS Variables + Transform = Individual Properties by Dan Wilson (@danwilson) on CodePen.
\nWe set up a key initial transform
for our element with all the variables we intend to change. By modifying the variable value on the different states we can get a CSS rule that looks closer to our original code but with much more flexibility as the complexity grows. In this example we are dealing with many more states than the original example's three by introducing JavaScript (but this is not required: Here is a CSS only version of our original button example). Still, it effectively is one CSS property defined, and we change only one transform function at a time (whether that be in JS or CSS).
Without the CSS variables we would have to do some calculations on the current transition when changing one of the transform functions (which may not be trivial). Then we could know where the other two functions are currently to make sure the transition remained smooth. Every time a variable is changed, a new transition occurs in the same way as if you had rewritten the transform
property from scratch, so you still can't have different transitions for each transform function.
x
, y
, and z
since we choose how to set up our initial transform
.Please reach out if you have some creative uses for this technique.
\n", "date_published": "2017-02-21T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2017/01/different-css-motion-path/", "url": "https://danielcwilson.com/blog/2017/01/different-css-motion-path/", "title": "A Different CSS Motion Path", "content_html": "When I first heard about the Web Animations API a few years ago, the most exciting piece was the promise of support for motion path animations - so that we could move/translate elements along a defined path instead of the linear default.
\nSVG has had this for years in most browsers, but the ability to use this with any DOM element brought new possibilities. A lot changed after the initial announcement, and a lot more has changed again, and there are a lot of exciting things to come from this spec. To see some examples of the current spec, see my demos and others' demos in Chrome 55+ or Opera 42+.
\nFor now, I want to discuss what I had originally hoped the CSS Motion Path module would be (using CSS + SVG animations to illustrate, since this doesn't really exist).
\nInstead of defining a path for an element and then animating its offset value to create motion along the path, I feel my ideal CSS Motion Path would tie more into existing transitions/animations.
\nFor example, take a basic translation animation with a 2000ms duration with linear easing and an infinite iteration count via transform: translate(25rem, 7.5rem)
:
For example:
\ntranslate-path: path("M0,100 C200,0 200,200 300,0")
This is where I would pass the hard stuff to the browser (and why I understand this is a complicated issue). Assuming this could be achieved well, I'd expect the path to be rotated and scaled such that its start and end points match the start and end points of the translation.
\nSo with the following CSS:
\n.circle {
animation: go-forth 2000ms 0ms infinite linear;
translate-path: path(\"M0,100 C200,0 200,200 300,0\");
}
@keyframes go-forth {
100% { transform: translate(25rem, 7.5rem); }
}
\nWe could have the following animation (second dot is the reference of the default translation):
\n\nThen if we only change the start and end points of our translation to be further apart and from the top/right to the bottom/left (with no change to anything related to the path) such as by changing our keyframes
to:
@keyframes go-forth {
100% { transform: translate(-26rem, 52rem); }
}
\nWe'd have:
\n\nThere are several use cases for the current spec, and in many ways I'm glad it is how it is. There's still a part of me, though, that finds more value in what I've just described. I will certainly admit that might just be due to habit - being used to animating transforms for the last several years.
\n", "date_published": "2017-01-02T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2016/08/why-waapi/", "url": "https://danielcwilson.com/blog/2016/08/why-waapi/", "title": "When to Use the Web Animations API", "content_html": "Firefox 48 is now available, and with it comes the third browser (after Chrome and Opera) to support an initial feature set of the Web Animations API. With it, Brian Birtles goes into detail why this API is a big deal. A few people have recently asked me why I talk so much about the API but still use CSS animations a lot of the time... so with Firefox's official release, here are some thoughts on when using JS might make more sense than CSS.
\nBut first, a spoiler... I still typically prefer CSS for animating. You state an animation once, and all matching selectors will pick up on it without you having to iterate through them all. There's a reason over time we've seen things move from JS to CSS, and for me it's hard to beat declaring some rules in CSS and calling it a day.
\nOne of the main reasons to reach for JavaScript is when values are not static. For a basic Transition, you can use JS to modify a value to a transition
property dynamically in one line such as element.style.transitionDelay = '123ms'
and that's fine enough. However, modifying keyframe values on the fly is much more involved, such as adding a new stylesheet to the document with your new keyframes fully defined.
var frames = getFramesArray(); //get your new frames, e.g. [{offset: .5, transform: 'scale(1)'}]
animation = '@keyframes updatedAnimation {';
frames.forEach(function(frame) {
animation += (frame.offset \\* 100) + '% {transform:' + frame.transform + '}';
});
animation += '}';
var style = document.createElement(\"style\");
style.appendChild(document.createTextNode(animation));
document.head.appendChild(style);
var ele = document.getElementById('toAnimate');
ele.style.animationName = 'updatedAnimation';
\nInstead the API can handle this directly, and in one spot.
\nvar frames = getFramesArray(); //get your new frames, e.g. [{offset: .5, transform: ''}];
var ele = document.getElementById('toAnimate');
ele.animate(frames, 1000);
\nMaking randomized confetti, snow, and springy objects often requires less tinkering when using the API.
\nSee the Pen Confetti by Dan Wilson (@danwilson) on CodePen.
\nFor the confetti demo I actually combined both WAAPI and CSS. I used the WAAPI to generate random scale and horizontal placement values (also duration and negative delays), while animating inner divs with a 3D rotation and a wavering back and forth via CSS. I could have taken it further and animated everything with random values, but having only the container based on random values (for the primary confetti action of falling to the ground) was enough for it to feel spontaneous.
\nAnd yes, I agree - the phrase "primary confetti action" is one of the least confetti-related phrases a person can say.
\nIf you're already doing a lot of logic to add and remove classes to trigger your CSS transitions or animations, the API might be a good way to go as well. That way all of your logic is in one place instead of two. Especially, if you are also needing to listen to animation events like animationend
or transitionend
. That is usually a sign to me that the only reason I'm using CSS is to take advantage of hardware acceleration (which you will get from browsers that support the WAAPI already).
See the Pen 755de1ba31b1629e4f662db0818185ac by Dan Wilson (@danwilson) on CodePen.
\nWhen you want to change the rate or be able to jump to different frames, the API will be the easiest way to do that now. It's all technically achievable with CSS now, but you have to take a fair amount of extra care to get timings right.
\nAnother reason is if you have complex animations that depend on other animations for when to start. The next level of the spec is introducing formal grouping and sequencing for multiple animations, but even now using the callbacks, delay
, and endDelay
s in the WAAPI can be a lot easier than managing your sequence of animations via delay
s alone in CSS.
An excellent CSS-Tricks article comparing various animation methods (by Sarah Drasner) covers the cons for CSS in detail.
\nSee the Pen Falling Letters by Dan Wilson (@danwilson) on CodePen.
\nI've used the Web Animations in a couple client projects because they were basic enough, but still benefited from the playback rate and timeline control.
\nYou can also use features like canceling, playbackRate
variance, dynamic elements/values, onfinish
handlers, and more to create a game. The CodePen version of a game I recently created is shown here, with the full version now at Letters and Such. Effectively, letters (or other characters) fall, and you have to type the appropraite key before they reach the ground. Playback Rate is increased over time, letters and locations are randomized, and if the correct key is typed the animation is canceled (and you get a point, to boot). If the key is not pressed in time, however, the onfinish
handler will be called and a "miss" is counted. The code in the CodePen is commented as far as the logic is concerned, so take a look for more detail.
There is nothing that would prevent you from making this with CSS animations. It made sense for me to keep the animations primarily in JS, especially since the animations drive a lot of the actual game logic.
\nThe Web Animations spec that underlies the Web Animations API is all about uniting JS and CSS methods. Firefox Nightly builds actually allow you to get all Animations on the document via JS, whether generated with the WAAPI, CSS Animations, or CSS Transitions. Right now some of the properties on CSS ones are read only (no changing of duration, for example), but already you can read/write the currentTime, pause these animations, or change playbackRate. There will be even less to differentiate the methods beyond personal preference as the spec matures.
\n", "date_published": "2016-08-04T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2016/06/pointer-events/", "url": "https://danielcwilson.com/blog/2016/06/pointer-events/", "title": "A Mouse, Some Touches, and All Sorts of Pointers", "content_html": "If you've ever tried to support both touch and mouse events on the web you know there is a lot to consider, and you can get tripped up - especially when you accept that it is not an either/or situation. Pointer Events try to unify different input pointing devices so you don't have to think as differently for touch and mouse (or a stylus... or anything new that comes like an airtap), while also giving you access to the details that might be unique to each type.
\nPointer Events have been around since 2012 and the introduction of Windows 8 with IE 10. Having used them when I made Windows apps for the launch of the Windows Store, I've been excited about their potential. They abstract enough of the similarities between different pointer types while still providing pressure on a device that allows it.
\nWith their introduction recently in Chrome 52 behind a flag, they now are on track to be in three stable evergreen browsers (with support already in Edge and Firefox (behind a flag since last August)). We'll take a look at how to use them, how to enable the flags, and how to provide a fallback to mouse + touch in other browsers.
\nPointer Events provide a single event for mouse + touch + stylus + other future pointing devices. They have common mappings to Mouse Events, such as if you currently listen to mousedown
you would instead listen to pointerdown
.
See the Pen Pointer Event Logging by Dan Wilson (@danwilson) on CodePen.
\nThese events can be triggered: pointerdown
, pointermove
, pointerup
, pointerenter
, pointerleave
, pointerout
, pointerover
, pointercancel
In addition to the normal event properties (such as pageX
) when an event fires you also are given the following:
pointerId
: a unique identifier (Integer) so that you can keep track of the touch with which you are dealingpointerType
: a string such as "mouse" or "touch"pressure
: a number between 0 and 1 for the pressure if the input and device support it (0 represents a hover, and 0.5 will be the default for an input + device that does not support pressure but is pressing)tiltX
: number in degress (from -90 to 90), visually described in the spectiltY
: similar to tiltX
, also visually described in the specwidth
and height
: dimensions (in CSS pixels) of the pointer. This will likely 1x1 for a mouse and something bigger for a touch, but is left to the browser to determine how to report itbutton
and buttons
: shows which button is changed and all buttons pressed, respectively.pointerId
and pointerType
are the two properties that will appear regardless of the capabilities of the pointer.
There are three pieces you will want to remember when using Pointer Events:
\nwindow.PointerEvent
is present.Element.addEventListener('pointerdown', function(e) {})
touch-action
CSS property to none
(or other value than auto
) on the element(s) that you want to receive Pointer Events.It's the third point that needs the most explanation. If you do not set this in CSS, your pointer events will fire fine for mouse, but might do nothing on touch (as determined by each browser). This is because the browser likely already has a lot of touch gestures that perform actions at the browser level, such as scrolling and zooming the viewport. By setting touch-action: none
you are telling the browser to not perform those browser-level actions on the element and instead trigger Pointer Events. There are other values that will allow you to continue some browser behaviors and then fire a subset of events.
See the Pen CSS Touch Action by Dan Wilson (@danwilson) on CodePen.
\nFor example, you could still want normal scrolling/panning on a given element, but still want to know when a pointerdown
occurs via a JS event. In that case you could set touch-action: pan
. You will no longer receive pointermove
events for every finger movement. You could even limit this to a direction such as touch-action: pan-y
, which will fire pointermove
events for horizontal moves, but perform the default browser behavior for vertical movements (with no JS events sent).
Edge will not fire any Pointer Events if touch-action
is auto
, while Chrome and Firefox both currently treat auto
similar to pan
. So it is best to know and specify what you want for consistency between browsers.
As of today, you can explore their uses in the following browsers
\ndom.w3c_pointer_events.enabled
and layout.css.touch_action.enabled
flags in about:config
Pointer Events
flag at chrome://flags/#enable-pointer-events
The fullest representation is Edge. In my testing Chrome and Firefox work well with a mouse or single touch point already, but have issues to resolve with multitouch.
\nAlmost everything in Pointer Events has a fallback either through Mouse Events or Touch Events. For example, Touch Events have an identifier
that acts like pointerId
and force
which is similar to pressure
.
I've not seen an equivalent for tilt*
, so pen support will lack angles without Pointer Events. Though, it’s important to note tilt
is only reported on a subset of pens (even the Surface Pro 4 pen does not support it in Edge).
We can use our support check to provide fallbacks
\nif (window.PointerEvent) {
//register event listeners for Pointer Events
} else {
//register for mouse and touch
}
\nHere is a full demo with multitouch and fallbacks. Press and the circles will surround your pointer. Release and they will fade away. If your pointer and device support it, more pressure/force will make the circles grow larger. 3D transforms occur when tilt values are detected.
\nSee the Pen Press & Hold by Dan Wilson (@danwilson) on CodePen.
\nThe caveats for this demo are the flagged releases of Chrome and Firefox. The Pointer events work well for mouse, pen, and a single touch point. When multiple touch points occur in Chrome and Firefox currently (flagged versions 52 and 48, respectively), it cannot keep up. Both browsers have work left before they become unflagged, and you can follow the status/bugs for both Chrome and Firefox for updates.
\n", "date_published": "2016-06-14T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2016/03/animations-and-promises/", "url": "https://danielcwilson.com/blog/2016/03/animations-and-promises/", "title": "Promises in Web Animations", "content_html": "When I first started learning about the Web Animations API there was one way to handle a "finish" event - the onfinish
callback. It lets you assign it a function and that function is called when the animation is finished.
There is a newer way to handle this scenario, though... via the finished
Promise. This either excites you (if you know what a Promise is), confuses you (if you know what a Promise is), or makes you ask what a Promise is. So...
Promises have gone through several specs and implementations. A standardized version has found its way into most browsers with Edge, Firefox, Safari, Chrome, and Opera's latest all supporting Promises. They are a feature in JavaScript similar to a callback in that they look for some code to finish running and then run some other code. With callbacks we often think of success and error handlers... with Promises we think of resolving and rejecting, such as:
\n//set up 1 second animation to fade box out
var box = document.getElementById('box');
var animation = box.animate([{ opacity: 1 }, { opacity: 0 }], 1000);
function finishedHandler() {
console.log('animation finished: ' + Date.now());
}
function canceledHandler() {
console.log('animation canceled: ' + Date.now());
}
//the Promise version, log the timestamp when the Animation finishes
animation.finished.then(finishedHandler, canceledHandler);
//the effective successful equivalent with the onfinish callback
animation.onfinish = finishedHandler;
\nThis code starts an animation (by calling animate()
on an element), and any other code will continue to execute while we wait for the animation to enter the "finished" state. Animations will expose a finished
Promise that will "resolve" when the animation finishes. You can think of the Promise as a future object. When it resolves (in this case, a successful finish of the animation), the first function passed into the then
will run. If the animation is rejected (the animation is canceled), it will run the second function passed to then
, if specified.
For rejection, the current spec defines it this way, but there are discussions to change away from this. We will focus on how to work with an animation that resolves for now...
\nPromises by definition will either resolve or be rejected, and this resolution will only happen once. As soon as a Promise is resolved or rejected, it will not resolve nor reject again. Since animations can be played multiple times, this seems to be at odds with the Web animations API as subsequent finishes would not resolve again.
\nTo solve this, the spec dictates a new Promise will replace the existing finished
Promise when the play state changes to "running". This allows you to latch on to the Promise resolution state again, but you will have to add your handlers via then
again.
//extending the previous example, play the animation second time
animation.play();
//set 'then' to what spec states will be a newly created Promise
animation.finished.then(finishedHandler);
//the onfinish from before remains
\nNote: As of March 18, 2016 the polyfill has an issue with resolving subsequent finished Promises at the correct time.
\nImagine a case where you want to trigger new animations when a given animation finishes. The following example shows two boxes, one using onfinish
to start new animations and the second using the finished
Promise.
See the Pen Callbacks vs. Promises with Web Animations API by Dan Wilson (@danwilson) on CodePen.
\nCallbacks will nest, and Promises can chain by returning a Promise. For this example it reads differently, and one might be more readable than the other to you. In fuller development the chaining can provide more power with how you manage your animation states and values. We will look into that in a future post.
\nThere is a lot more to talk about with Promises... for much fuller discussions on Promises check out the Ponyfoo article on the topic and the MDN article, especially their suggested reading.
\nThanks to Rachel for triggering this discussion and Šime for pointing me to when an animation is rejected in the spec (and Jake for mentioning the discussion about changing that). Thanks also to Martin for recommending additional Promise articles.
\n", "date_published": "2016-03-18T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2015/12/columns/", "url": "https://danielcwilson.com/blog/2015/12/columns/", "title": "Of Layouts with Columns", "content_html": "I've been wanting to try this alternate large-screen layout out for a while, but there were quirks (more on those later) I hadn't resolved so I waited. Now that I (potentially) have resolved the issues I'm just going to flip the switch and see how these columns work out!
\nMobile is always where I first read something, but I know that especially in the tech industry we still read on large monitors while working. It's weird that we have these large monitors but we still set max-width
s on our content and force the user down a page. So... I'm trying out columns on my articles when you have a large and wide screen. Full width and horizontal scrolling - why not?
Columns, grid layouts, and other new methods for laying out pages have been emerging the last few years. I'll quickly admit I have not done user research or anything to validate what I'm trying here... it's simply my current exploration as to what we can try. Hopefully it works well for you, and if it doesn't I'd love to hear thoughts on why.
\nInitially I'm rolling this out to what I (somewhat arbitrarily) deem a large enough viewport (1600x900), via the following media query:
\n@media screen and (min-width: 100em) and (min-height: 56.25em) {
/* ... */
}
\nI also look to see support in browsers. The following should support all browsers that support CSS.supports
and column
(as well as IE 10 and 11 that have unprefixed column
support but lack CSS.supports
):
if ((window.CSS && window.CSS.supports && (CSS.supports('column-width', '1px') || CSS.supports('-webkit-column-width', '1px') || CSS.supports('-moz-column-width', '1px')))
|| (document.body.style.columnWidth === '')) {
document.documentElement.classList.add('columns');
}
\nThen I do some width/padding/position updates to the body and containers to prepare them for being a page with height 100vh and scrolling horizontally, as well as adding the column properties to my article
. There are also rules set up to try to prevent breaking before a paragraph that follows a heading:
.columns article {
/* also include -moz and -webkit */
column-width: 58rem;
column-gap: 5rem;
height: calc(100vh - 24.5rem); /* take full height less the header and footer */
}
.columns p + h3 {
break-after: avoid; /* not well supported outside IE/Edge */
}
\nI had planned to roll this out when I reworked my site a half year ago, but that was when I started using a lot of CodePens in my articles. Columns do not respond well to changes to content once they have rendered (in the case of CodePen a link changes to a module with a different height and different break
rules). If the length of your content changes after the column is painted the browsers are not responding to that change, and you will potentially have your last column(s) chopped.
I'd known about this issue since I first tried columns a few years ago... but I still forget about it. I have now learned that both browser resizes and changes to the column
properties will cause the browser to repaint, so my current solution is to load the CodePen script and attach an onload
event listener. That is still to soon to act, so for now I am resorting to a setTimeout to make it work (in my case a change to the column-gap
by .1rem does the trick).
(function() {
function onCodePenLoad() {
setTimeout(function() {
var article = document.querySelector('.post-content');
article.classList.add('external-loaded'); //this class just changes the column-gap values by .1rem
}, 1000);
}
var cp = document.createElement('script'); cp.type = 'text/javascript'; cp.async = true;
cp.src = '//assets.codepen.io/assets/embed/ei.js';
cp.onload = onCodePenLoad;
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(cp, s);
})();
\nThere are many people talking about alternative screen layouts and the new CSS that is here and coming to help us. I highly recommend CSS Layout Weekly from Rachel Andrew and catching up on podcasts and talks from Jen Simmons of The Web Ahead.
\nShould be a fun time ahead on the web.
\n", "date_published": "2015-12-04T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2015/09/animations-conclusion/", "url": "https://danielcwilson.com/blog/2015/09/animations-conclusion/", "title": "Web Animations API Tutorial Conclusion", "content_html": "This is the conclusion to an introductory/tutorial series on the Web Animations API coming to browsers. I've updated the series content in June 2016, as Chrome and Firefox have both rolled out major updates (and some small spec changes). If you have thoughts/questions or see that I’ve misinterpreted the spec, please reach out to me on Twitter, @dancwilson.
\nWe've covered a fair amount of ground and hopefully cleared up questions about what the Web Animations API is (and is not). To conclude this series, we'll recap what we've discussed and take a look at what has yet to be implemented.
\nIn the introduction we discussed how the API came into being as a way to unite the various methods to animate in CSS, JS, and SVG... with the intent to take the best of them all. This means, for example, JavaScript can latch on to the hardware acceleration that CSS has had for years, and you are not limited to the declarative nature of CSS. The API is not meant to replace libraries like GSAP, but simply to provide more options at the browser level.
\nFirefox and Chrome have both started implementations, and Edge has it on its backlog. The polyfill allows us to start playing with it today as the team finalizes the specification.
\nTo create a basic animation, we follow a structure similar to CSS by providing keyframes and timing properties.
\nvar player = document.getElementById('toAnimate').animate([
{ transform: 'scale(1)', opacity: 1, offset: 0 },
{ transform: 'scale(.6)', opacity: .6, offset: 1 }
], {
duration: 700,
});
\nTimeline controls are the obvious pieces not currently present in CSS. Reading the state of an animation happens via the playState
property, and changing the state is via methods such as play()
, pause()
, and finish()
. We can also change the playback rate to be slower or faster with the read/write playbackRate
property. The currentTime
can be checked (or changed), and we can set a callback for when the animation finishes with onfinish
.
The Web Animations API allows for multiple animations on an element, creating separate animation objects. The default timeline on the document
gives us access to all animations created with the getAnimations()
method. Animations can be grouped to play together or one after the other by using GroupEffects and SequenceEffects (available in the polyfill but not in the first spec).
Animating along a path sneakily saw its first implementation in CSS during this series, but there are many other pieces yet to come.
\nThe current implementations use a default spacing if no offset
s are set in the keyframes, meaning they are evenly distributed (e.g. three frames will have offsets of 0, .5, and 1). The specification also defines a way to pace the animation based on a property, so that it has a constant rate of change. The spec describes this well when discussing Spacing Keyframes.
The spec has evolved to include a ready
Promise that will be replaced with a new Promise every time the animation is cancelled or enters a pending state (which will typically happen before changing to "running" or "paused"). In addition to using the onfinish
callback as we have discussed in this series, we will be able to use the finished
Promise to run a function after an animation finishes.
People are starting to talk more about this API, and I hope this conversation continues. The spec, browser implementations, and polyfill have been going on for a while, and they are ready to examine closely.
\nSometimes CSS will make the most sense, sometimes requestAnimationFrame
will, and sometimes using a library will be the best solution. It's good to know when to use what, and this API provides quite a few things that were not previously available to us by default... so have fun.
Check out the rest of this series:
\nThis is Part 5 of an introductory/tutorial series on the Web Animations API coming to browsers. The spec (and Chromium implementation) for Motion Path has changed significantly since this article was written. The following is still valid conceptually, but property names and more have changed. For the latest, please check CSS Motion Path as of October 2016.
\nFinally. Animating along a path... no longer just the domain of SVG.
\nSee the Pen Motion Path Infinity by Dan Wilson (@danwilson) on CodePen.
\nAs the API specification has been worked through, motion path has appeared in different forms. One direction that initially seemed likely was the form of an Effect (like the previously discussed GroupEffect), but then a little bit of steam picked up with Motion Path as a CSS Module (with its own spec).
\nTherefore animating along a path will simply be another set of CSS properties that can be animated, just as opacity
and tranform
can. This way CSS Transitions and Keyframes can use it as well as the Web Animations API... which is great since we want as much as we can shared between these methods to give us more flexibility. Chrome and Opera have released an initial implementation, so we can actually start playing with it today even though it has not found its way to any polyfill.
Let's break down what these properties are, how we can use them, and what things might still trip us up for now.
\nThere are three motion
properties we will discuss. For now, to see the examples you will need to be running Chrome 46 or Opera 33.
motion-path
#The starting point is motion-path
which defines a path along which the element can move, following how paths work in SVG 1.1:
#motioner {
motion-path: path(\"M200 200 S 200.5 200.1 348.7 184.4z\");
}
\nThis can also take a fill-rule
as an optional first parameter in the path call. I recommend reading Joni Trythall’s excellent Pocket Guide to Writing SVG for a discussion on what this entails.
According to the spec, you can also use a basic shape such as circle
, polygon
, ellipse
, and inset
. These should look familiar to you if you have tried using CSS Shapes.
With Blink's initial implementation, I've only seen the path()
method work, so either I've been using shapes incorrectly or that is yet to come.
motion-offset
#To drive the motion and actually place the element somewhere on the path we use motion-offset
. This can either be a double length value or a percentage. So to animate from the starting point of the path to the end we set up an animation that goes from 0 to 100%. With the Web Animations API we have
var m = document.getElementById('motioner');
m.animate([
{ motionOffset: 0 },
{ motionOffset: '100%' }
], 1000);
\nAnd with CSS
\n#motioner {
animation: path-animation 1s;
}
@keyframes path-animation {
0% {
motion-offset: 0;
}
100% {
motion-offset: '100%';
}
}
\nSee the Pen CSS Motion Path Spiral by Dan Wilson (@danwilson) on CodePen.
\nThis CodePen demo shows several dots moving along a spiral path from the outside to the inside. As each dot approaches the center it gets faster, smaller, and becomes transparent. .animate()
is called twice on each dot with inifinite iterations and a delay, where one call focuses on the motion offset and the other focuses on the scaling and opacity. I broke them out to specify different easings, but they certainly could have been combined.
This approach also uses feature detection, which you will notice if you are viewing this in Safari, Firefox, Edge, or an older Chrome/Opera because you will see a message instead of the animation. There a few ways to do this, such as
\nvar m = document.getElementById('motioner');
if (m.style.motionOffset !== undefined) { ... }
\nOf course, we wouldn't want to block out users completely in a real web thang, so we can have an alternate animation (or no animation) that switches to the Motion Path animation if supported. Progressive Enhancement is a friend here, as usual.
\nmotion-rotation
#The final property is motion-rotation
, and it deals with which direction the element "faces" as it moves along the path. There are four primary ways to specify this.
See the Pen CSS MotionPath by Dan Wilson (@danwilson) on CodePen.
\nThis is a first version, of course, and the browser makers and spec writers are still discussing it all. The biggest thing I have noticed while trying this out was the lack of a way to adapt the path to different screen/container sizes.
\nPaths simply appear as they are defined. When working with SVG we get flexibility because we have different coordinate systems and attributes on the container like viewBox. With Motion Paths defined in CSS, the size of the path cannot be additionally modified or constrained with other properties. The width and height defined on your element apply only to the element, not its motion path. You could use media queries or JavaScript to define different paths for different criteria, but setting it up with flexibility via the motion
properties is not possible yet.
We will see the direction the specification takes, but for now it's fun to try this out and see what it might provide (and not provide). I'm gathering a collection of CSS Motion Path demos I find on CodePen, and Eric Willigers (owner of this implementation task on the Chrome development team) has a Google Doc with examples.
\nWe will wrap up this series next time by recapping what we have discussed and taking a look at a few currently-only-in-the-spec topics.
\nCheck out the rest of this series:
\nThis is Part 4 of an introductory/tutorial series on the Web Animations API coming to browsers. I've updated the series content in June 2016, as Chrome and Firefox have both rolled out major updates (and some small spec changes). If you have thoughts/questions or see that I’ve misinterpreted the spec, please reach out to me on Twitter, @dancwilson.
\nLet's continue our discussion on multiple animations within the Web Animations API by discussing a few pieces available today in the polyfill to provide grouping and sequencing.
\nA KeyframeEffect
takes three parameters: the element to animate, an array of keyframes, and our timing options. These are all parameters we've seen before when using element.animate()
. This new object essentially is a blueprint for a single animation and we will see it as we discuss ways to group and sequence animations. It does not start an animation, it just defines one.
var elem = document.getElementById('toAnimate');
var timings = {
duration: 1000,
fill: 'both'
}
var keyframes = [
{ opacity: 1 }.
{ opacity: 0 }
];
var effect = new KeyframeEffect(elem, keyframes, timings);
\nThough not available in any native implementation yet, and not even found in the Level 1 spec, the polyfill provides a way to group animations and play them together. The GroupEffect
(yes, it is coming in the Level 2 spec) groups one or more KeyframeEffect
s to play simultaneously.
See the Pen zGeVey by Dan Wilson (@danwilson) on CodePen.
\nA GroupEffect
takes an effects parameter where we can pass in our array of KeyframeEffect
s representing our multiple animations. Once defined, we can play the group of animations on the default timeline when ready.
var elem = document.getElementById('toAnimate');
var elem2 = document.getElementById('toAnimate2');
var timings = {
duration: 1000
}
var keyframes = [
{ opacity: 1 }.
{ opacity: 0 }
];
var kEffects = [
new KeyframeEffect(elem, keyframes, timings),
new KeyframeEffect(elem2, keyframes, timings)
];
var group = new GroupEffect(kEffects);
document.timeline.play(group);
\nSimilar to the GroupEffect
, SequenceEffect
s allow us to group together multiple animations (specified by KeyframeEffect
s)... but instead of playing them in parallel they play one after the other. You can also, as defined in the polyfill, use GroupEffect
and SequenceEffect
together (such as having a grouping of multiple sequences).
See the Pen vNYQLL by Dan Wilson (@danwilson) on CodePen.
\nSequences give you something that we've had to work our way around with CSS or with what we've seen with the animations API so far. We would have to maintain delays based on duration of earlier animations or use finish
callbacks. These methods can be hard to maintain and may not be as precise.
Using the earlier variables from the GroupEffect
code segment:
var sequence = new SequenceEffect(kEffects);
document.timeline.play(sequence);
\nWe have previously looked at the element.animate()
way to create animations. This is the quick way to create an animation, play it immediately, and get a reference to the Animation
object. We focused on this first since it has been supported in Chrome for a while now, as well as the polyfill. Firefox is the first to support an alternate way: the Animation
constructor. It shows us one more way to use the KeyframeEffect
, and it is in the Level 1 spec so we should see more use of this soon.
First a reminder of how element.animate()
works:
var elem = document.getElementById('toAnimate');
var timings = {
duration: 1000,
fill: 'both'
}
var keyframes = [
{ opacity: 1 }.
{ opacity: 0 }
];
elem.animate(keyframes, timings);
\nUsing the same variables as above, the following is equivalent using the Animation
constructor:
var kEffect = new KeyframeEffect(elem, keyframes, timings);
var player = new Animation(kEffect, elem.ownerDocument.timeline);
player.play();
\nThe primary difference here is that the animation does not start playing immediately, so this will be useful when creating animations in advance to be played later.
\nAs the Level 2 spec makes its way through the working drafts, we should see more definition on these different Effects. There are two more planned posts in this series. Next time we will take a look at the future again with what else we can expect to see soon.
\nCheck out the rest of this series:
\nAbout a year ago I bought a Tessel and a few NeoPixel combinations to start developing beyond the screen. Since I hadn't done any soldering since my Theremin craze, I had to get familiar with that again, but otherwise it was largely straightforward to jump in. I made a few small things like a "wreath" that changed colors depending on what holiday you were currently approaching. There was also what I called a baby monitor that used the ambient module to listen for noise (such as a cry) and fade in a soothing color and then fade it out.
\nMy most recent small project revolves around a light show. My wife wanted to buy something that would cast light against a wall and move it to entertain our son. I suggested we try to build it ourselves with the Tessel, and now I have the initial version available on GitHub. Yes, we are talking kid eyes, and NeoPixels are bright, so it's important to note this is a proof of concept only. The ideal way to do this ultimately would be to put the Tessel and NeoPixels behind an object such as a dresser. That way you are just admiring the glow against the wall.
\nHere is the current code in action (not behind the dresser, simply to show what the NeoPixels are doing):
\n\nI'm taking a 60-LED strand of NeoPixels, using the handy Npx abstraction for working on Tessel, and setting up a 60 frame animation. I divide the strand into sections of ten NeoPixels, assign a different color to each section, and then move them one by one in each frame.
\nfunction colorWave() {
for (var c = 0, len = NEO_LENGTH; c < len; c++) {
var anim1 = npx.newAnimation(1);
anim1.setPattern(initialPattern(c, NEO_LENGTH));
npx.enqueue(anim1, DELAY);
}
run();
}
function initialPattern(start, length) {
var ra = [];
for (var i = start, l = start + length; i < l; ++i) {
var imod = i % length;
/* set color based on segments of 10, and push to array */
}
return ra;
}
function run() {
iterations++;
npx.run().then(function() {
if (iterations < 20) { //run this again
run();
} else { //turn off
npx.enqueue(npx.newAnimation(1).setAll('#000000')).run().then(function() {
npx.queue.pop(); //remove the \"OFF\" animation for future plays
});
}
});
}
\nThe most interesting part was getting a server running so that I could pass commands to it to play and stop. Take a look at the code on GitHub (and the README) for the example. With these pieces in place I can push this code my Tessel, the animation will queue up, the server will set up, and then I can go to my controller page, press Play, and the light show begins.
\nThat's it for now, but it got me thinking about other projects for parents. What I'd like to do next is also have the Tessel (and ambient module) listen for noise activity and push instances to Parse to chart out those times. The hypothetical use case here being for tracking a baby crying and how many times he or she woke up in the night.
\nFitbit for baby. Because parents never remember how many times they got up the previous night.
\nWhen I do that I'll post about the process of sending data from the Tessel as well.
\n", "date_published": "2015-08-20T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/2015/08/animations-part-3/", "url": "https://danielcwilson.com/blog/2015/08/animations-part-3/", "title": "Web Animations API Tutorial Part 3: Multiple Animations", "content_html": "This is Part 3 of an introductory/tutorial series on the Web Animations API coming to browsers. I've updated the series content in June 2016, as Chrome and Firefox have both rolled out major updates (and some small spec changes). If you have thoughts/questions or see that I’ve misinterpreted the spec, please reach out to me on Twitter, @dancwilson.
\nAfter our discussion on the Animation's controls and timelines within the Web Animations API, let’s talk about multiple animations.
\nSee the Pen Multiple animate calls by Dan Wilson (@danwilson) on CodePen.
\nIn this example, each rectangle has three animations applied (affecting transform, opacity, and color). You can call animate()
on an element multiple times, similar to CSS allowing multiple animations.
With CSS:
\n#toAnimate {
animation: pulse 1s, activate 3000ms, have-fun-with-it 2.5s;
}
@keyframes pulse { /* ... */ }
@keyframes activate { /* ... */ }
@keyframes have-fun-with-it { /* ... */ }
\nWith the Web Animations API:
\nvar animated = document.getElementById('toAnimate');
var pulseKeyframes, //defined the keyframes here.
activateKeyframes,
haveFunKeyframes;
var pulse = animated.animate(pulseKeyframes, 1000); //the second parameter as a number is a valid shorthand for duration
var activate = animated.animate(activateKeyframes, 3000);
var haveFunWithIt = animated.animate(haveFunKeyframes, 2500);
\nWith the Web Animations API, this creates three Animation objects that can each be paused, played, finished, canceled, and manipulated via timeline or playback rate.
\nAnimation
s, Get Them All #So you've got an animation kicked off and playing, but you didn't capture the Animation
reference when you called animate()
on the element. What’s a person to do?
See the Pen PqgKVK by Dan Wilson (@danwilson) on CodePen.
\nThe spec allows for a method getAnimations()
on the document. In the latest version of the spec it is directly on the document (document.getAnimations()
) which is how it is implemented in Firefox 48+. However, as of Chrome 52 and the polyfill (as of v2.2.0), it is implemented according to the old spec which placed it on the new timeline
object on the document.
//If including the polyfill you can use the following
var animations = document.getAnimations ? document.getAnimations() : document.timeline.getAnimations();
//returns array of all active (not finished and not canceled) animations
\nIn the CodePen example, you will see several dots moving with random durations, delays, and transforms with an infinite duration. The 'Pause All' button calls getAnimations()
and iterates through all the returned players (one for each animation) and pauses each one.
In the next part, we'll look at the different ways a WAAPI animation can be created (because there's more to it than just element.animate
). Hint: document.timeline
will make more appearances.
Check out the rest of this series:
\nThis is Part 2 of an introductory/tutorial series on the Web Animations API coming to browsers. I've updated the series content in June 2016, as Chrome and Firefox have both rolled out major updates (and some small spec changes). If you have thoughts/questions or see that I’ve misinterpreted the spec, please reach out to me on Twitter, @dancwilson.
\nNow that we understand how to create a basic animation with the Web Animations API, let’s talk about states, controls, callbacks, and timelines.
\nWhen you call element.animate()
, an Animation
object (formerly called an AnimationPlayer
in the spec) is returned and the animation starts playing. To see the current state of the animation you can check the readonly property playState
which will return one of five strings. We can also modify the current state of the animation, by calling one of four methods:
var player = element.animate(/* ... */);
console.log(player.playState); //\"running\"
player.pause(); //\"paused\"
player.play(); //\"running\"
player.cancel(); //\"idle\"... jump to original state
player.finish(); //\"finished\"...jump to end state
\nIn addition to running
, paused
, idle
, and finished
play states there is a pending
state defined that will occur when a play or pause task is pending.
This Walking Circles example shows six circles scaling. You can pause or play each circle to see a portion of the above play states in action.
\nSee the Pen Blob That Walks by Dan Wilson (@danwilson) on CodePen.
\nIn the previous CodePen example, there is also a "2x" button that you can press to change the playback rate of the animation, switching it to double speed. This is through the read/write playbackRate
property.
var player = element.animate(/* ... */);
console.log(player.playbackRate); //1
player.playbackRate = 2; //double speed, can also be decimal to slow it down.
\nWith CSS transitions, there is an event that typically fires when the transition ends. Similarly, the Animation
allows you to specify an onfinish
function when the animation either completes or you call the previously discussed finish()
method. Note that according to the specification an animation set with an infinite number of iterations cannot be finished, nor can one with a playbackRate
of 0. There is also an oncancel
handler, and the option of using Promises for when the Animation
finishes.
The following example uses onfinish
to display some stats once the animation completes (and it also segues nicely to the next discussion on timelines).
See the Pen Timer Countdown by Dan Wilson (@danwilson) on CodePen.
\nEach Animation
exposes two read/write time-related properties - currentTime
and startTime
. For now, we will focus on the former.
currentTime
returns the milliseconds where the animation currently is. The max value will be delay + (duration * iterations), so infinite iterations will not have a max value.
var player = element.animate([
{opacity: 1}, {opacity: 0}
], {
duration: 1000,
delay: 500,
iterations: 3
});
player.onfinish = function() {
console.log(player.currentTime); // 3500
};
\nPlayback rate will affect how quickly the timeline continues. If you set a playback rate of 10, your max currentTime remains the same, but you will go through the timeline 10 times faster. This concept is also shown in the earlier Timer Countdown example.
\nSince currentTime
is read/write, we can also use this to jump to a certain point in the timeline. It can also let us synchronize two animations, as shown in the next example.
See the Pen Syncing Timelines - WAAPI by Dan Wilson (@danwilson) on CodePen.
\nreverse()
#You can also reverse an animation with reverse()
which will be very similar to play()
(such as it will have the same playState
) except it will traverse the timeline in reverse. When an animation finishes the currentTime
will be 0.
See the Pen waRKOm by Dan Wilson (@danwilson) on CodePen.
\nThat was a lot of information, but it was still just getting familiar with what is available. We'll take a look at some more advanced usage next time.
\nCheck out the rest of this series:
\nThis is Part 1 of an introductory/tutorial series on the Web Animations API coming to browsers. I've updated the series content in June 2016, as Chrome and Firefox have both rolled out major updates (and some small spec changes). If you have thoughts/questions or see that I’ve misinterpreted the spec, please reach out to me on Twitter, @dancwilson.
\nWe’ve taken our initial look at the Web Animations API, but we didn’t get into any real specifics… so let’s dive in now.
\nThe WAAPI gives you more control than you might be used to with CSS Animations, but before we get into these extras, we need to set up the baseline: How do I create a basic animation through this API?
\nIf you are familiar with CSS Transitions and/or animations this will look familiar.
\nvar player = document.getElementById('toAnimate').animate([
{ transform: 'scale(1)', opacity: 1, offset: 0 },
{ transform: 'scale(.5)', opacity: .5, offset: .3 },
{ transform: 'scale(.667)', opacity: .667, offset: .7875 },
{ transform: 'scale(.6)', opacity: .6, offset: 1 }
], {
duration: 700, //milliseconds
easing: 'ease-in-out', //'linear', a bezier curve, etc.
delay: 10, //milliseconds
iterations: Infinity, //or a number
direction: 'alternate', //'normal', 'reverse', etc.
fill: 'forwards' //'backwards', 'both', 'none', 'auto'
});
\nFor comparison’s sake, here is an equivalent CSS Keyframe animation
\n@keyframes emphasis {
0% {
transform: scale(1);
opacity: 1;
}
30% {
transform: scale(.5);
opacity: .5;
}
78.75% {
transform: scale(.667);
opacity: .667;
}
100% {
transform: scale(.6);
opacity: .6;
}
}
#toAnimate {
animation: emphasis 700ms ease-in-out 10ms infinite alternate forwards;
}
\nWe’ll break this down and explain each piece.
\nvar player = document.getElementById('toAnimate').animate()
\nAnimating will return an Animation
(formerly known in the spec as an AnimationPlayer
) that will let us do fun things later, so you’ll likely want to set up a variable to capture this reference. We find the element we want to animate (here simply with document.getElementById
) and call the animate
function. This function is newly added with the spec, so you will either want to test it for support/existence before using it or include the polyfill.
The animate
function takes two parameters, an array of KeyframeEffect
s and AnimationEffectTimingProperties
options. Essentially the first parameter maps to what you would put in the CSS @keyframes
, and the second parameter is what you would specify with the animation-*
properties (or the animation
shorthand, as in my earlier example) in your CSS rules. The key benefit here is that we can use variables or reuse previously defined KeyframeEffect
s, whereas with CSS we’re limited to the values we declare upfront.
var player = document.getElementById('toAnimate').animate([
{ transform: 'scale(1)', opacity: 1 },
{ transform: 'scale(.5)', opacity: .5 },
{ transform: 'scale(.667)', opacity: .667 },
{ transform: 'scale(.6)', opacity: .6 }
]);
\nFor each KeyframeEffect
, we change the percentage offsets we have in CSS to a decimal offset
value from 0 to 1. It is optional though, and if you don’t specify any they will be evenly distributed (so if you have three, the first has an offset of 0, the second has an offset of .5, and the third offset = 1). You can also specify an easing
property that is the same as the animation-timing-function
in CSS. The other properties in each KeyframeEffect
are the properties to animate. The values for each property should match how you would specify them in JavaScript using element.style
, so opacity
will be a number, but transform
will expect a string.
var player = document.getElementById('toAnimate').animate([], {
duration: 700, //milliseconds
easing: 'ease-in-out', //'linear', a bezier curve, etc.
delay: 10, //milliseconds
iterations: Infinity, //or a number
direction: 'alternate', //'normal', 'reverse', etc.
fill: 'forwards' //'backwards', 'both', 'none', 'auto'
});
\nThe timing properties will map to CSS animation properties, though sometimes with different names. The earlier code sample discusses the primary options.
\nHere is an example that uses the polyfill (but if you are viewing in Chrome 36+, Opera 23+, or Firefox 48+ it should be using the actual browser implementation). The first column grey blocks are animating with the WAAPI, and the second column red blocks are animating with CSS keyframes.
\nSee the Pen CSS Keyframes v. WAAPI by Dan Wilson (@danwilson) on CodePen.
\nNow that we know how to make an equivalent animation from what we know in CSS, we’ll start looking at the Animation
object that the animate function returns. This is where we see the real features and improvements.
Check out the rest of this series:
\nThis is an introduction to a tutorial series on the Web Animations API coming to browsers. I've updated the series content in June 2016, as Chrome and Firefox have both rolled out major updates (and some small spec changes). If you have thoughts/questions or see that I’ve misinterpreted the spec, please reach out to me on Twitter, @dancwilson.
\nIn Summer 2014, Google announced Material design with a representation in web through Polymer... using a polyfill for the upcoming standard Web Animations API.
\nI hadn't heard of this API, but I was intrigued, especially since it talked about a MotionPath effect. That wasn't implemented yet (you'll find out what happened in Part 5), but its goal of providing a way to unite the CSS, JS, and SVG ways to animate kept me interested. A year later and Chrome and Firefox have started implementations, the polyfill's progress is steady, and it's time to take a look at it in earnest.
\nBut so few people are talking about the WAAPI! My hope is to start a series of posts highlighting the features that are in browsers (and the polyfill) now, exploring why we want this API, and figuring out the nuances. Hopefully we will also get more people discussing, and working with, this API.
\nWe'll start this exploration by figuring out what it is and what it is trying to accomplish.
\nAnimation has progressed nicely in the last half decade, with great CSS support and new additions to improve JavaScript. But each approach to animation still has a slew of cons to all the pros they provide.
\nrequestAnimationFrame
has good support and lets the browser optimize when to animate, but it can still hang up if there is a lot of other JavaScript running. It also often requires more math to get timing down.setInterval
introduced many developers to animations, but it is imprecise and can lead to stuttering animations.jQuery.animate()
introduced several other developers to animations, but often has performance issues.In general, we like it when browsers support as much as they can, and they take over the optimizations. Browsers now have document.querySelector
because we saw the value jQuery provided to select DOM elements. So utilities in libraries moved into the browser natively. Ideally, we could pack as much animation control at the browser level. These libraries can then focus on providing newer features, and the cycle can continue.
The Web Animations API tries to do this. It aims to bring the power of CSS performance, add the benefits and flexibility of JavaScript (and SVG animation, which we will talk about in a future post), and leave it to the browsers to make it work well.
\nAt a former job, we received an email stating that they knew we had too many places to check for company announcements - email, monitors in the office, Yammer, Google Chat, and a intranet/wiki. So to solve the problem they announced... they were adding a blog.
\nMy first thought hearing about the Web Animations API was the same thought I had hearing my company was adding a blog - this will only make things worse. Sure enough, the blog didn't centralize anything, it just added one more place we had to check for news, and it died out.
\nThis feels different, though. Reviewing a spec (first time I've really done it to this extent) shows the attention put into this. It's not meant to replace existing behaviors (although some browsers are deprecating some it seems...), but instead unite the various ways and allow them even to interact. The syntax is similar to CSS but adds the options of variables, controls, and finish callbacks.
\nSo the Web Animations API is new, and starting to be implemented (currently in Chrome and Firefox) in addition to having a polyfill. Next time we will actually start looking at what it provides developers... with examples!
\nCheck out the rest of this series:
\nI looked online. I found a few sites that did what I wanted. None of them worked well on mobile.
\nI wanted to know how many days it had been since a given date... so here it is: How Many Days Since?. It's not fancy. It only has one page. But it does what I needed on all my devices... including using pushState to make the URL bookmarkable.
\nSometimes a need is simply something a first semester web student can do. But I wanted one with a little bit of style, so with just a quick CodePen and a push to my server I now have one that suits my exact need... and maybe yours, too.
\nA well respected web developer has been quoted as saying, “This is the best one of these on the internet!”
\n", "date_published": "2015-06-29T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/60/", "url": "https://danielcwilson.com/blog/archives/60/", "title": "2015", "content_html": "Facebook is for seeing photos of strangers' kids and pets that my friends have liked.
\nTwitter is for seeing people talk about the Apple Watch.
\nFlipboard's Daily Edition is for finding depressing news. And a fun gif at the end.
\nYahoo News Digest is for finding more depressing news. And a fun quote or stat at the end.
\nLinkedIn is for recruiters to still associate me as a Java developer.
\nNotifications only exist to disable.
\nGitHub is for people to wonder why I haven't responded to their comment and threaten to no longer use my plugin.
\nThe Texas grocery store freezer section is for people to write prayers for a company that has recalled its ice cream.
\nMy iPhone is for controlling my Chromecast.
\nMy Chromecast is for watching Netflix.
\nNetflix is for watching shows I already own on DVD but can't get because I'm holding a baby who doesn't want me to move a single, solitary inch.
\nHammocks, however, I understand.
\n", "date_published": "2015-05-16T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/52/", "url": "https://danielcwilson.com/blog/archives/52/", "title": "The Upcoming iPhone 6: Everything we know so far!", "content_html": "Nothing.
\n", "date_published": "2014-06-25T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/47/", "url": "https://danielcwilson.com/blog/archives/47/", "title": "Implications of an Open Sourced WinJS", "content_html": "The big news (to me) out of Microsoft's Build conference was that Microsoft is open sourcing WinJS. I think it is the impetus for the other big announcement about "universal apps" running all the way from phones to Xboxes.
\nWinJS was introduced a couple of years ago as a library to develop native Windows 8 apps using typical front-end web tools. I've talked about it here and there on my blog, and in further detail during my recent SXSW talk about developing responsively for Windows 8.
\nThe main weak point of WinJS was that you could only use it on Windows 8 and 8.1. Not Windows Phone and not in the browser. Releasing on multiple platforms and prototyping functionality quickly in the browser were out of the question. This open sourcing, in theory, changes that. Now a single codebase should be able to work well across devices and even platforms.
\nI make no secret that I am a web developer, not a Microsoft developer. The fact that they are reaching out to web developers is fantastic. Their vision for Windows across platforms is right in line with what I love about the web.
\nAs a web developer, we can make no assumptions about how a person views our content. For a brief time it was common practice to assume "small screen" and "touch" were equivalent. Or that "mobile" meant "320px." We're smarter than that, and with Windows 8.1 plus these upcoming changes, Microsoft makes sure we assume nothing. Windows 8.1 already has great handling of touch, stylus, and mouse... abstracting out what can be and allowing for specific gestures when possible. Keeping an adaptive input approach (as discussed by Jason Grigsby) in mind is key to a successful app across the myriad of devices available. Throwing speech and Kinect gestures to the forefront of Windows, too... well it admittedly makes it more difficult to develop yet, if done well, more satisfying for the user.
\nI feel like we are at a point where handling screen size is the easy part of successful web development. Making it look great everywhere is one thing. Making it work great everywhere is the challenge... and the fun.
\n", "date_published": "2014-04-02T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/44/", "url": "https://danielcwilson.com/blog/archives/44/", "title": "South By Reflections", "content_html": "\n\n Even though I walked around SXSW the last four years picking up Gowalla shirts\n and free tacos (breakfast and otherwise), 2014 was the first SXSW I went to as\n an attendee. There are countless people bemoaning that it has lost its spark\n (though I think most people are recycling their articles from last year... or\n the year before... or the year before...). Not only did I attend, I also had\n the pleasure to give my first presentation at a conference. Here are a few\n observations and things I appreciated about SXSW.\n
\n\n\n The fact the organizers can maintain order across three full festivals and so\n many smaller ones simultaneously amazes me. \"Maintaining order\" actually\n undervalues it greatly because schedules are kept, sessions begin promptly,\n and the various venues remain in sync. The volunteers and organizers deserve\n an amazing amount of credit for that.\n
\n\n\n There was one hiccup during the prep for the first day of workshops where the\n online sign up list was not working properly. But the organizers worked off\n printouts and the delay was negligible.\n
\n\n\n Leading up to my session I was, to be honest, regretting submitting the talk.\n The only comments I saw on Twitter about the session were snark filled...\n unavoidable I suppose, especially when the word \"Windows\" gets tossed around.\n And as someone who has thirty followers and does not get involved heavily on\n Twitter, I'm not used to seeing the public negative thoughts of something I\n put a lot of effort in to create. Ultimately this was just something that\n caught me off guard a bit. In fact, this might be the one topic I encountered\n not yet discussed on the excellent\n Ladies in Tech podcast\n about\n speaking at conferences.\n
\n\n\n Still, the more I thought about it the more I thought my topic was so niche\n that no one would come out. Lo and behold they did, and I even saw smiles and\n nods. And nobody yelled at me when I stumbled at the end when looking at code\n while realizing I was running out of time and unsure how to adjust. And nobody\n threw their Surfaces at me when I admitted I was a web developer first,\n Windows 8 developer second (or maybe third)... or when I almost certainly\n mispronounced XAML as I explained I would not be talking about it because I\n didn't know what it really was. I think being honest up front about my\n perspective helped set the tone for the session so no one was surprised when I\n only talked about JavaScript and not anything.NET. In summary, even if my talk\n was more ramble than not (as it seemed to be in my head), I feel I had several\n good points and the feedback has been invaluable. Thanks to everyone who came,\n and thank you to others for the encouragement.\n
\n\n\n Yes. There is a lot of marketing to be found. That may or may not have been\n the case ten years ago. To someone just walking around that might be all they\n see. To someone who doesn't understand the session titles talking about some\n really cool technology or idea that might be all they see. There are two main\n points though about the marketing I noted: You can avoid it... and some of it\n is done well.\n
\n\n\n First of all, just avoid it if you don't want to see it. Stay in the\n convention center or the other venues and watch Neil deGrasse Tyson... or\n check out\n Josh Clark's\n fantastic ideas on connecting devices through the web... or learn how\n companies like Sparkbox are\n using (the rather forgotten practice of) apprenticeships in the world of the\n web. Or you can even stumble upon something that has nothing to do with your\n day job or normal interests... like I did when I happened upon\n Travis Swicegood's talk\n about how the\n Texas Tribune is using\n public data records to create their great visualizations and more. It might\n seem hard to maneuver, but it's also really easy to know what to avoid when\n you see a ton of signs and bright colors.\n
\n\n\n Secondly, not all marketing is bad. Sure, we're humans and being told how to\n think is not our favorite. But what I appreciate is seeing large national\n brands supporting and getting the name our of smaller local/Texas companies.\n Nest served\n GoodPop popsicles. Yahoo!\n had Austin Amber from\n Independence Brewing. AT&T (and I think eighteen other companies) had\n Chi'lantro tacos. How\n great is that? Everybody is going to see them, and of all their options they\n chose (I assume?) to go with local companies for their giveaways.\n
\n\n\n I had fun, learned a lot, missed a lot, and simply became too tired to really\n enjoy any of the free shows for the Music festival. But that's okay as I\n completed one of my goals for the year (present at a conference). It was a\n great place to start that journey as well, with this festival that could have\n easily outgrown itself years ago but continues to flourish as it grows bigger\n and bigger.\n
\n", "date_published": "2014-03-21T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/38/", "url": "https://danielcwilson.com/blog/archives/38/", "title": "Windows Store Development Resources", "content_html": "\n\n I’ve lamented before how it is hard to find information about details of\n developing for Windows 8. It’s great that we can use front end technologies to\n build apps, but the fact that there are three languages to develop in divides\n the available information.\n
\n\n\n I am leading a workshop session (Taking the Responsive Web to Windows 8) for SXSW 2014. While working on my slides and a sample project, I am trying\n to find details that I’ve never seen or found once a long time ago without\n remembering the source. Every once in a while I find the right search term\n (Windows 8? Windows Store? WinJS? IE 11? The Non-XAML Way of Doing Something?)\n and stumble upon something great.\n
\n\n\n I plan to write more about Windows over the coming months, and will try to\n point to these great sources I find.\n
\n\n\n I knew I read somewhere that IE does not follow the “use 3D transforms to\n trigger hardware accelerated transitions” model that Webkit has been using for\n years. Before putting this in my talk I needed the source. After 4 hours of\n searching, I found a great description of\n Performance with Windows Store apps. Among other good information,\n animations are discussed. In short, any transition on opacity or transforms (3D or 2D) will cause the\n animation to run independently of the main UI thread.\n
\n\n\n The second resource I found today is one that I surely should have found\n sooner.\n David Washington discusses\n seemingly-not-well-documented pieces of IE/WinJS/Windows like\n chained panning/zooming and snap points. I’m just beginning to review these, but they look informative.\n
\n", "date_published": "2014-02-03T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/34/", "url": "https://danielcwilson.com/blog/archives/34/", "title": "Path to Palindromes: Puzzles and the Web", "content_html": "\n\n Path to Palindromes debuted this past week on\n iOS\n and\n Android... for free. This is my first attempt at developing a game, and it was\n admittedly a lot of fun. I used to tell people I don't have games on my phone,\n but really it's just that they are all in word/number puzzle form.\n Letterpress\n and SpellTower are two of my\n favorites, and both begin simply as a grid made up of letters.\n
\n\n\n The objective: Find as many palindromes as possible within a grid of numbers\n before time runs out. A little math/vocabulary refresher... a palindrome is a\n number (or word) that reads the same backward as forward, such as 989 or\n 358853 (or mom or racecar). Score by connecting any adjacent/diagonal numbers\n to form a palindrome. The longer the palindrome the more points you get.\n Simple... as far as rules go.\n
\n\n\n To create the game, I used some familiar (to me) tools:\n Cordova for packaging the app for the AppStore\n and Google Play, Lavaca for\n client side MVC, and\n Hammer.js\n for gesture abstractions (because dragging is so much simpler with it,\n especially since I am completing a version for Windows and Windows Phone which\n use the much-preferred-by-me pointer events spec instead of touch events).\n
\n\n\n Running the app on the iPhone or iPad will have some iOS7-esque things...\n namely transparencies, blurs, and layers. The app is one main screen with\n three actionable items that when tapped load a new screen on top of the main\n screen. Since I kept it simple with no left-to-right transitions, I was able\n to blur the main screen while the selected \"sub screen\" is maximized. This\n involves a transition on the scale and opacity of the selected screen while\n simultaneously transitioning the main background to a blurred version.\n
\n\n\n While you can animate CSS filters, I assumed (yeah... I'll do a test someday)\n that it's not great for performance (going from filter: blur(0) to filter:\n blur(40px) for example). Therefore I used a variation of a technique on the\n great\n CSS Tricks\n using two stacked backgrounds (my main background and the blurred version of\n it) with the help of opacity, transition, and ::after. Is it perfect? No. Do I\n feel it's close enough since it only happens for .35 seconds as a new section\n is coming in? Yes.\n
\n\n\n See the Pen\n Blur background by\n @danwilson on\n CodePen.\n
\n\n\n I had the idea for this game in August, and took a vacation day to see if I\n could get it working in a day. I got very close: The basic mechanisms, style,\n and tapping the board to select tiles worked (and this was before iOS 7\n designs came into play)... but I got caught up on being able to simply drag\n through the tiles to play. I wasn't using hammer for one, so I was tracking\n everything myself. But there were two bigger pieces that tripped me up: How to\n undo and how to make sure when you play diagonally with a finger that you\n don't accidentally select nearby tiles.\n
\n\n\n So I stepped away and revisited it in December by scrapping everything and\n starting over. This time I went in with Hammer so I didn't have to be\n creative. Ultimately I went with a solution that puts an invisible element\n inside each tile that is a circle, so the selection will not trigger until the\n finger is over that smaller circle.\n
\n\n\n The rules then became if you are in \"dragging mode\" and your finger is over\n the selectable circle, follow these rules:\n
\n\n Once this got a clear answer the game only a couple days of development. I\n kept the game simple for a reason. Thanks to Cordova plugins that installed\n without issue (Lee Crossley's\n iOS GameCenter Plugin\n especially) and great support for the web on iOS7 and Android KitKat, I was\n able to submit both to the AppStore and Google Play after a couple more days\n testing.\n
\n\n\n In my head, the web always seemed like it would be perfect for little puzzle\n games, and this experiment proved me right, thankfully. If you want to play\n online, I am also making the\n game available in the browser, since I talk about liking the web so much. Admittedly, I've not tested it\n much in older browsers, but I'm releasing it more as a testing exercise...\n which is why I waited so long to mention it.\n
\n\n\n Five years ago, I feel something like this wouldn't have made sense with\n front-end web technology. But with a solid Javascript architecture, CSS\n animations and transitions, and people thinking about how to support multiple\n inputs like mouse and touch, it's now possible to develop well-performing\n games in the browser.\n
\n\n\n If you have any feedback on the game, or if you want to share other examples\n of browser puzzle games, reach out to me on\n Twitter. I hope you\n like it.\n
\n", "date_published": "2014-01-20T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/20/", "url": "https://danielcwilson.com/blog/archives/20/", "title": "Other Browsers, Keyboards, and Height", "content_html": "\n\n As a followup to my article on\n iOS 7's keyboard and height problems, I\n thought I'd check in with other browsers... and I was left more confused than\n before. I am using the same demo page as my\n last article for these other browsers with an element positioned absolute at\n the bottom. I always thought I knew how this worked, but I was very wrong (or\n everyone has changed how this worked, and I missed the conversion day). I\n still need to test what has the potential to be the strangest of all... the\n stock Android browser (the \"IE 6\" one).\n
\n\n(Updated November 19, 2013: Screenshots added)
\n\n\n The keyboard here appears above the fixed content (as I would hope), and the\n window and document heights appear unchanged (as I also would hope). I like\n it.\n
\n\n\n\n\n Visually, this actually behaves similarly to Windows 8's browser. The\n document's height stays the same, so the keyboard is on a layer above the\n footer, as I would expect. It differs from Windows 8, however, in that the\n window height does actually change. So with the keyboard up, the window height\n will be effectively the document height less the keyboard's height.\n
\n\n\n\n\n I was surprised to see behavior that more closely matches the busted iOS 7\n WebView: The fixed footer stays attached to the top of the keyboard. As far as\n heights are concerned - the document height here is the one that shrinks while\n the window's stays the same. This is the opposite of the Windows Phone test,\n and in my head makes less sense.\n
\n\n\n I will need to rerun this test now that upcoming versions of\n Chrome will allow homescreen apps, similar to iOS. It should also be noted that the latest Android Firefox\n (25.0) also pushes up the footer with the keyboard.\n
\n\n\n Unsurprisingly (when you know that both Chrome and Twitter - and Facebook,\n Flipboard, etc. - just use a WebView with some different controls than\n Safari), both of these behave exactly as the busted WebView.\n
\n\n\n\n\n I've always hated in-app browsers because they have always been just a poor\n man's version of the actual Safari browser. Still... they remain in heavy use\n since they are embedded in some very popular apps (as\n noted by Luke Wroblewski) and some people falsely believe that iOS Chrome uses its own Chrome-like\n rendering engine. With iOS 7 these limited browsers become much poorer.\n
\n", "date_published": "2013-11-14T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/18/", "url": "https://danielcwilson.com/blog/archives/18/", "title": "iOS 7, Keyboards, and Height: A Lesson in Confusion", "content_html": "\n\n It might be a bug, or it might be an intended change… but the keyboard affects\n iOS web views in new ways with iOS 7.\n
\n\n\n Taking a look at three different ways to view a web page, we see different\n behaviors for the height of the document:\n
\n\n\n Check out this iOS 7 keyboard issue demo in\n both iOS 7 Safari and as a fullscreen app saved to the home screen. You can,\n of course, also check it out in other browsers where a keyboard comes up on\n the screen including earlier iOS versions.\n
\n\n\n Obvious cases are elements that are positioned absolute or fixed at the bottom\n (shown on the demo page). They will float up with the keyboard and potentially\n cover up the focused input. This is all the more likely to be annoying on a\n 3.5-inch screen (or… change to landscape). The demo page will act a little\n buggy if you try this in landscape because if you tap the text field, the\n keyboard will come up and the footer will cover up the field. The focus event\n is never actually triggered, but the keyboard is up and there is a cursor in\n the covered field… though you likely will not be able to type in it.\n
\n\n\n Use of flexbox can also create similar unintended scenarios. Additionally,\n people are also seeing the\n power of min/max-height. Depending on what changes in the media query, this issue could cause\n jarring results to the user.\n
\n\n\n So... I'm not sure where we go from here. I've not found much discussion of\n this (hence my write-up). If you have thoughts or find new information, let me\n know on twitter (@dancwilson). I'll follow up with anything I find.\n
\n", "date_published": "2013-10-10T14:21:23Z" },{ "id": "https://danielcwilson.com/blog/archives/17/", "url": "https://danielcwilson.com/blog/archives/17/", "title": "Taking the Responsive Web to Windows 8... and 8.1", "content_html": "I've worked on three Windows 8 apps using HTML/CSS/JavaScript (one largescale for a client and two smaller apps of my own), and I started all of them before Windows 8 was officially released. Going into the projects one of my favorite features was the multitasking views, showing two apps side by side. It seemed ripe for responsive web design...
\nWe made mistakes.
\nMicrosoft had a key recommendation early on to support these multiple "view state" scenarios: Take a more "adaptive" approach where you target the four view states specifically using media queries with four specific -ms-view-state values (fullscreen-landscape, fullscreen-portrait, filled, snapped). Snapped view was always 320px wide. Filled was always the device width minus 320px. Portrait was always fullscreen. Landscape was also fullscreen horizontally. While we all felt (on the client project) that a more responsive approach was better and future friendly, based on Microsoft's recommendation + an aggressive timeline + little documentation and community to find support at the time we decided to take this differing approach.
\nThis favorite side-by-side approach for apps in Windows 8 received an important advancement in Windows 8.1, one that fits in much better with 2013. Snapped view is no longer a guaranteed 320px. When you launch apps side by side they are by default each 50% width. Launch another and they fit into thirds. Want to make one slightly bigger? drag it and it is done. The world of four fixed states is gone, and an app can fit any number of widths and heights. Great for the user? Definitely. Great for the developer stuck in self-described 2010-fixed-width glory? Not at all. Great for developers excited about the real world where devices range from tiny to huge, and everything between and beyond? Yes.
\nAnd is it great for developers like me who followed the Windows 8 recommendation? Sure, because it reminds me to listen to myself and do things right the first time. Windows 8 was a big unknown in the summer of 2012. We knew you could develop with web technologies, and there was a WinJS library. Beyond that we didn't know if coding it like a typical single-page website was appropriate. There was the thinking that beyond using JavaScript, we needed to rethink how we worked. This turned out to be again a lesson in overthinking. If you can use HTML/CSS/JavaScript... then use it in the way that makes sense. We followed the recommendation that ultimately was not in line with our current thinking, and now we get to revisit everything and rework it. We could have coded everything without using the four view states and had it work the same in Windows 8 and been prepared for the change that eventually came in Windows 8.1. Alas.
\nMost of these changes are highlighted in Microsoft's documentation.
\nFirst of all, the four specific view states in media queries are deprecated. Snap view even has a new minimum width of 500px (though you are able in your app's manifest to change it to 320px if desired). So they will continue to work today, but may not work in the way you expect if you target your app to the 8.1 SDK. Instead, use the more common media queries for min- and max-width (and the same for height).
\nSimilarly, the ApplicationView.value property in JavaScript is deprecated. So instead of checking against this property to see which view state you were in, you directly fetch the document.documentElement.offsetWidth. PageControls in Windows 8 had an updateLayout method that would be called as a view state changed. I've not been able to find a mention of this being deprecated, but it appears the new suggested approach is simply to add a listener for the window's resize event.
\nThere is no more unsnapping. Well... at least it, too, has been deprecated. We used this one a few times in our client app. Click on an item while in snap view and the app will, as expected, try to make your app be the on in the filled state, and shrink the other to snap view. It worked well, but Microsoft clearly wants your app to work regardless the width/height to which it is constrained. Reasonable.
\nDo it right. Use relational units. Minimize our use of WinJS-y methods when there is a standard web alternative (unless there is a performance gain, etc.).
\nAlso, with the old four view states, the thinking was often keep portrait, landscape, and filled relatively the same (scrolling horizontally as all good Windows 8 apps should), but make the snap view flow vertically (as the best Windows 8 apps did). With the increased minimum width, the vertical flow does not make as much sense always. So we are experimenting with always scrolling horizontally, adjusting list styles/sizes, and placement of flyouts, etc. Only time (and users) will tell what approach makes the most sense on this new platform.
\nThe one thing we know for sure is that this approach for Windows 8.1 is a welcome change (even if we have to rework based on the original Windows 8 implementation/recommendation). It's a step in the right direction.
\n", "date_published": "2013-10-10T07:33:10Z" },{ "id": "https://danielcwilson.com/blog/archives/16/", "url": "https://danielcwilson.com/blog/archives/16/", "title": "Succeeding (with a Caveat) with Lavaca, Cordova 3.0, and BreweryDB", "content_html": "I've said it before, and I'll say it again - JavaScript MVC Frameworks are not my thing... typically. MVC is logical to me, but doing so much on the client-side often leads developers to only support a limited set of browsers/devices. Hesitations aside, I've found places where I feel MVC frameworks can shine. Only want to support hybrid apps where you take a native app and package web views in some fashion? You already are limiting the browsers and devices, so this can work. I won't go into the business decision to go down this route in the first place - but assuming this is your requirement, then a JavaScript MVC can be a great way to go.
\nAll this to say - I've created an app called Local on Tap to experiment more with the MVC framework Lavaca, the latest release of Cordova, and a crowdsourced database for craft brewing called BreweryDB. I released it to the iOS App Store a month and a half ago, with the Android version coming soon (after a little more real device testing).
\nLavaca has come a long way since its first version a year ago. If you've got Node installed, setup is quick, and the integration with Cordova is nice (although it was a lot more useful before Cordova 3.0's changes). Pull down a starter project, npm install, run "grunt server"... and you've got an app running on a local server. Run "grunt build" and you have a minified codebase ready to deploy to a server.
\nLavaca has several recommendations stated out of the box with the starter app, but I chose to go a different route in many regards. There is a HeaderView that acts globally and based on Model changes will change the title and potentially determine whether back buttons should show, etc. I chose to keep the headers inside each View. This gives more flexibility in my mind, and also works more nicely with iOS 7 where headers and the status bar are tied together... and more closely tied to each individual view.
\nAs far as working with it, it follows a fairly typical MVC pattern. You define Controllers that route to different Views with different Models, all while maintaining state, history, etc. The key to a successful Lavaca project might be in proper usage of the View's redraw method. To keep the app feeling snappy, as you click on links, I direct the user immediately to the next screen... even though the content for that screen is usually unknown and being fetched by an API call. After the new screen has completed its "enter" event and the API has returned with the appropriate data, the screen (or typically, just a section of the screen) is redrawn with the new content. Too much of this (or while animations and other events are occurring) and there can be flashes and a stuttering feel. Keeping it after "enter" and only doing the redraw once keeps things feeling responsive.
\nI'm a visual guy, and command line has never been my friend. College professors swore that even when coding in object oriented languages, vi was the only true text editor. I never bought into it, but I'm starting to understand the benefits of command line tools. Adding a plugin is easy (if you know the git URL) and building a project is easy. Lavaca used to do a lot of this for us, but now Cordova does it on its own, and it's a welcome change. I've not yet experimented much with the merges folder, but it seems like a useful way to get around anything that requires it. Though, hopefully nothing does.
\nI love the idea behind this service... and its reliability. They support the little known breweries, and since data is often populated by the fans of these breweries, it often is quick - even shortly after a tweet goes out about a new offering. The API is easy to work with and gives a solid amount of information. It is one of the best documented and well organized APIs I have worked with in a while.
\nI wanted to do this project quick and without a server component (to play with Lavaca), and since the BreweryDB service does not support CORS, packaging the app with Cordova seemed like a solid way to go. This, as stated at the beginning, means my requirement was for a limited set of devices.
\nAfter working through this project, I feel this lack of broad support is my only hesitation to do this more often. I'm a web developer, and any time I only see something work on an Apple device or something that supports touch I wonder why bother with the web. The web should be for everyone... and it's not hard to support them all. But you have to choose the right tools. And build the right way. And progressively enhance properly. And love the world.
\n", "date_published": "2013-10-07T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/14/", "url": "https://danielcwilson.com/blog/archives/14/", "title": "Using Lavaca to Start a New Mobile App: Part 1", "content_html": "\n It goes without saying, so I’m going to say it - there are a lot of frameworks\n out there to handle front end (JavaScript) MVC. For every\n Backbone there is an\n Angular or\n Ember... but my current go-to\n is the relatively new Lavaca.\n I aim to show you how to get your own Lavaca project up and running (I'll be\n basing this from Lavaca 2.1), but let’s start with some basic reasons why I\n currently use Lavaca over other options.\n
\n\n There are a few prerequisites, but if you have dabbled with another MVC\n library or Node, then you likely will already have everything you need.\n
\n\n You’ll want the latest stable version of\n Node and NPM. Oh, and a Mac.\n
\nThen it’s a matter of grabbing the Lavaca CLI tool in a Terminal window
\ncurl
https://raw.github.com/mutualmobile/lavaca/master/getlavaca >
/usr/local/bin/getlavaca && chmod +x /usr/local/bin/getlavaca<
\nOnce everything is installed...
\ngetlavaca
\n cd
directory-name
\n npm install
\n grunt server
\n You now have an example project running!
\n\n But we don’t want to just run in a browser... we want to build this as a\n hybrid application. Lavaca doesn’t assume anything at creation, so we need to\n tell Lavaca we want to build for specific platforms with Cordova.\n
\n\n This consists of installing the Cordova CLI and then initializing a fresh\n iOS/Android/etc. project as needed. Running the below commands by default will\n create a blank iOS project and a blank Android project. Read the\n Lavaca Guide to find out how\n to change that.\n
\nnpm install -g cordova grunt cordovaInit
\n\n This will sync Lavaca’s commands with the Cordova CLI and create source\n folders for both projects. The last command needed is\n
\ngrunt build
\n\n This will merge your HTML/CSS/JS with the Android and iOS src projects, by\n creating a new folder in your app’s directory called build. If dealing with an\n iOS project, for example, open build/ios/AppName.xcodeproj ... hit run... and\n you’ll see your app as packaged by Cordova. Part 1 is now complete.\n
\n\n Part 2 will review the basic structure of an app, and what it takes to replace\n the example app with your own.\n
\n", "date_published": "2013-08-14T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/12/", "url": "https://danielcwilson.com/blog/archives/12/", "title": "The Little (&) Barely Documented) Things in WinJS & IE - Handling Basic Input", "content_html": "When I first started working with Windows 8 in August 2012 (a couple of months before Windows 8 released), I was excited to start writing native apps with my beloved web technologies. I didn't know what was really required other than Microsoft had some library called WinJS and a very much improved version of Internet Explorer. So I opened up my browser and started looking at documentation.
\nOnly... this was next to impossible. There were a couple basic "Quickstart" tutorials and some bits of documentation, but finding information on these ListViews, Flyouts, and semantic zoom interactions was hard. Fortunately, a lot has changed in a year, and it's now a lot easier to find information about how WinJS handles something or what Internet Explorer 10 needs for a given CSS rule. Yet some concepts remain largely hidden or misunderstood. For my future reference as much as anything else, here I'm posting a series with some useful bits to remember when developing Windows 8 apps (which will translate to the browser as well since nothing below is specific to WinJS). This inaugural post focuses on the basics of handling input.
\nApple and WebKit got developers used to handling event touches (or targetTouches/changedTouches) on touchstart, touchmove, and touchend events. Microsoft does not support this, and instead implemented what is now a W3C Candidate Recommendation - Pointer Events. There are similarities to the WebKit way - there are pointerdown, pointermove, and pointerup events, for example. But Pointer Events take a different approach in that they support all types of pointer input.
\nMouse, touch, stylus - these are all covered by one event. There's no more worrying about handling touch one way and mouse another (and then worrying about if you properly disabled the corresponding mouse event when touch was used instead). If you need to distinguish, there is a pointer type inside the event to delegate different action paths.
\nI plan to write another post in detail about Pointer Events, but until then here are key articles to learn more.
\nMore so than mouse and stylus input, touch has several default gestures that perform actions. Scrolling/panning, zoooming, and other actions are defined by the OS. So if you have a block of content that has overflow scroll, when you move your finger in that block, the content will pan. We expect this as a user. But as a developer, there are cases where we want to turn off the expected behavior and handle it on our own with JavaScript.
\nThis is where the CSS property -ms-touch-action comes in.
\nWell, almost. First... an anecdote.
\nOur client for my first Windows 8 project wanted to turn our Semantically Zoomable ListView on the initial screen of the app to behave like the Windows 8 Start Screen. Seemed reasonable. Windows provided a ListView that looked like the tiles of the Start Screen. From there we just needed to add the abilities to toggle visibility and drag/reorder. Here's where we hit several walls. We finally found the early documentation of Pointer Events (not as thorough as it is now), but as our many unhit breakpoints made clear, our pointer event listeners were not triggering.
\nFinally we caught wind of a -ms-touch-action, which has a default value of auto. It sometimes seems backwards how this works, at least as it was originally presented. But once you understand the intent it becomes a lot clearer.
\nThe default value of auto means if a user performs a standard OS gesture, do what the OS wants to do and do not fire Pointer Events. You can also specify none or one (or more) of the following values:
\npan-x pan-y pinch-zoom manipulation double-tap-zoom (added for IE 11: cross-slide-x cross-slide-y)
Saying "none" will mean nothing will happen by default, but Pointer Events will be triggered for all touch interactions. Otherwise if you specify a specific value, only the specific value will be taken care of by the OS defined action (still with no Pointer events fired) while other gestures will fire the Pointer events. So if you specify only pan-x, then horizontal scrolling will work by default while dragging on the screen vertically will perform no action by default but will fire Pointer events.
\nFor a larger example, back to the anecdote of recreating the feel of the Start Screen, we wanted to handle drag/reorder but still allow horizontal panning, as the Start Screen does. Specifically with touch on the Start Screen, if you want to move a tile, you must perform what Microsoft calls a "CrossSlide" gesture. This is essentially dragging a tile vertically until it "snaps" out of place and can then be moved freely (as a sidenote, this gesture does a lot more and can be implemented in other ways, but this is sufficient for the purpose of reordering). So we had to set -ms-touch-action as pan-x, and then we set up pointer event listeners to handle movement in the vertical direction.
\nThe CrossSlide is an interesting beast (and IE 11 makes it a lot easier to deal with), and I plan to discuss it more, along with lessons I've learned from it in regards to WinJS development.
\nThere's obviously a lot more to be said about Pointer Events and gestures. I plan to get more into detail, especially breaking down the CrossSlide with examples (which will lean heavily on another barely documented item in WinJS... Gesture Recognizer).
\n", "date_published": "2013-07-31T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/8/", "url": "https://danielcwilson.com/blog/archives/8/", "title": "Missed Opportunities... On Repeat!", "content_html": "I'm content, but I'm not fully excited about my work.
\nI've got a website and online presence, but it's not representative of my latest skills.
\nI've got a resume, but it's not ready to send when needed.
\nI've got things to say, but I don't find the places to say them.
\nThese are all thoughts I've had in the past, and they are all thoughts that come back time and time again. At first glance they don't seem that important. At least there's something there for each... but they never really meet the potential. And ultimately the fact that each is often true at a given point means I miss opportunities. A dream job that opens up. A conference asking for proposals. I feel as though I'm always at a point where I am perfect for something that comes by, but I've not made that known to anyone but me. Which does not help me. By the time I have time to update my resume or type up a proposal the opportunity is gone.
\nI've been trying to find a way to break out from these missed opportunities and be ready the second something comes up. I have no idea if it will work, but at least I am committing to something and we will see what happens. Here's my current plan.
\nThese are things that really don't take much time, but the longer I wait between them the longer they take. Just like cleaning a shower.
\n", "date_published": "2013-07-06T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/6/", "url": "https://danielcwilson.com/blog/archives/6/", "title": "A new place and the same old issues", "content_html": "I started my first new job in almost five years recently. With it came a change from working with a single organization to the world of client services. Mobile services to be precise.
\nI'm on the newest team there - the Web Developers. It is always funny to me that the Web Developers are the new kids on the block in mobile (according to the majority of people with whom I interact). With the company telling us we have to agree and choose a framework such as Sencha Touch (since these directions are coming from people in the native world, not the open, progressively enhanced web world I like to frequent), there are many conversations that have taken place where tempers have flared a bit with different members. But something else has been bugging me much more, and it has occurred ever since I first learned HTML and took my first computer science class in college. People are ashamed of their code.
\nSince I started, I've heard multiple people tell me to forgive their code because it's not that great. If only I had had a different requirement I would have supported that browser earlier. I always told myself I would stand by my code, but when I heard myself make an excuse to my coworker for my Javascript ("Full disclosure: I've never worked with JQuery Mobile before, and that's what the original developer I took over for started with, so I think I completely butchered the MVC pattern that was trying to be achieved") I realized something was wrong, and it bothered me greatly.
\nIt might just be the organizations I have worked with, but I have always heard more people make excuses or claim they just wrote the worst code of the their ("but at least it works!") than those who are proud of their work. Why do we do it?
\nI hate feeling embarrassed to show my code to other people. But it happens. My first code review in the career world was horrible. I was proud of what I wrote, but then another developer was told to find at least five things to improve. Whether or not there was anything to really improve. So arbitrary best practices were thrown at me, and my non-technical boss looked at the review with disappointment in my skill. Beyond that, we would have demo days where we would share code with each other that we were proud of, and people would start interrupting ("you should have used a static final synchronized class instead of a static final class!").
\nFrom college and beyond, I've studied/worked with people who will look at legacy code and openly mock it - even if it's really not that bad. It's not in their preferred style so it must suck. Everyone does it. But so many times, the person who worked on the code is there. Maybe they worked on it five years earlier when they were a junior developer, forgive them. Maybe they were in a time crunch and management was pushing the timeline first, forget it. Maybe it's better than what you would have done, so be quiet.
\nSo I wonder if that's it. There are a lot of great developers out there who write great code. There are a lot more out there who write great code but who might have missed some optimization here or there. We should all be proud of what we do - especially if it works. There's always room for improvement - for everyone. Especially in this day and age. The iPhone has only been out for less than half a decade. Mobile computing is still young. Heck, the web is still young. What was great a great coding technique last year may be irrelevant the next. I take pride in seeing things work, and seeing users interact with my work. Even if I declare one Javascript variable in the middle of a method. Or if my previous life as a Java developer makes me mess up my terminology for Javascript functions.
\n", "date_published": "2012-01-10T00:00:00Z" },{ "id": "https://danielcwilson.com/blog/archives/3/", "url": "https://danielcwilson.com/blog/archives/3/", "title": "A new site and a new blog", "content_html": "Here we are with the first blog post on my new Wordpress setup on my recently updated site.
\nI've been doing a lot of reading on web design and mobile design over the last three years, but my job has kept me busy enough to not allow me to put all that I've learned into practice. I've certainly been able to put some things into play, but it's hard working for a large organization that views the web and mobile offerings as secondary. We were finally approved to begin work on a mobile web offering (we've had a website and iPhone/Android apps for some time now) three years after it was proposed and prototyped. And that's certainly fine, I understand approvals take time. It does amaze me though how people still do not understand why we would want to create a mobile web app.
\nAs a background, I work at a financial institution, and we are talking specifically about online banking. Transferring funds, viewing account details, opening accounts... you name it and we have it. We were even one of the first to use remote deposits from your computer (Scanning a check to deposit it), as well as on iPhones, Androids, and even the iPad 2 with their cameras and our apps. Our website has been converted over the last few years to use a lot of web standards and progressive enhancement to work across most browsers and screens. But it's still a pain to use on mobile devices. And because our business unit has talked to mobile app consulting firms, the belief is that we only need to focus on iPhone and Android through apps. Yet, literally everyday we get questions about why there's no way for Blackberry users to access our full site. Less often, but often enough we get requests for Windows Phone 7 and webOS users. We even get questions from our iPhone and Android users asking the same thing, as even though we have apps for them they 1) don't know about it or 2) simply would rather use the browser.
\nThere's a place for native apps, and I'm glad we have them, but it's only a part of the puzzle and we are missing out by not having a web version that is more usable for these smaller screens and different input features. It's not one or the other when it comes to apps - both have a place and purpose.
\nRegardless, here's my small site redesign that adapts with responsive web design and finally gets my thoughts out here with this blog. It's been a long time coming, but Spring is in the air with Summer to soon follow, so I shall make the most of it.
\n", "date_published": "2011-06-04T00:00:00Z" } ] }