96 stories
·
1 follower

Focusing a `background-image` on a Precise Location with Percentages

1 Share

Let's say you have an element with a background-image, where only part of the image is visible, because the image is bigger than the element itself. The rest is cropped away, outside the element.

Now you want to move that background-image such that you're focusing the center of the element on a specific point in it. You also want to do that with percentage values rather than pixels. We're going to have to get clever.

This is going to involve some math.

Let's use this as our image, which has markers for sizing:

And here's our element, with that background-image applied. Notice we can only see the (top) left of the image:

See the Pen Background Focus: no position by Jay (@jsit) on CodePen.

Now let's say we want to align a specific point on the image with the center of that element. Say, the point at 300px in from the left.

Since we're asking for this position in pixels, it's straightforward. With no position defined, the background-image "starts" with the point at 100 pixels at the center, so you need to move it to the left by 200 pixels:

See the Pen Background Focus: pixel position by Jay (@jsit) on CodePen.

Let's formalize it.

The x value you're using for background-position is calculated like so:

(0.5 × [bounding box width]) - [x-coordinate]
             0.5 × 200px -              300px
                   100px -              300px = -200px

It takes a second to figure out, but it's nothing too taxing. You could have probably figured that out intuitively without needing to use a formula.

But what if you wanted to (or had to) express background-position as a percentage? Shouldn't be too hard, right? Let's try using a percentage to get ourselves centered at 300px again. We had a background-position-x of -200px, so let's convert that to percent: -200 / 800 = -25%, so:

See the Pen Background Focus: percentage (1st attempt) by Jay (@jsit) on CodePen.

Hm. That didn't work at all. Maybe we need to use a positive value?

See the Pen Background Focus: percentage (2nd attempt) by Jay (@jsit) on CodePen.

That's better, but it's centered at, like... 250px? How about as a percentage of the bounding box width: 300 / 200 = 150%. That can't be right...

See the Pen Background Focus: percentage (3rd attempt) by Jay (@jsit) on CodePen.

Yeah, that's not right.

Let's back up. What happens if we do this?

See the Pen Background Focus: percentage (4th attempt) by Jay (@jsit) on CodePen.

That feels like it kind of makes sense; background-position: 100% 0; makes the background-image flush-right and centered at 700px, or 7/8 the width of the image. But what if we wanted to center it at 100%? I guess we'd have to do... 9/8?

See the Pen Background Focus: percentage (5th attempt) by Jay (@jsit) on CodePen.

At this point, I'm not surprised that didn't work.

This doesn't feel like the right path. Let's back up.

What does the spec say?

For example, with a value pair of '0% 0%', the upper left corner of the image is aligned with the upper left corner of, usually, the box's padding edge. A value pair of ‘100% 100%’ places the lower right corner of the image in the lower right corner of the area. With a value pair of '75% 50%', the point 75% across and 50% down the image is to be placed at the point 75% across and 50% down the area.

Maybe we can reverse-engineer this.

On that last one, 112.5%, it was aligning the point at 112.5% across the background-image with the point at 112.5% across the bounding box. That kind makes sense. The spec seems written to make it easy for only three values: 0%, 50%, and 100%. Any other value isn't so intuitive.

With background-position: 0;, we were focused on 100px, or 12.5%. With background-position: 100% 0;, we were focused on 700px, or 87.5%. How does background-position: 50% 0; look, exactly?

See the Pen Background Focus: percentage (6th attempt) by Jay (@jsit) on CodePen.

50% is kind of like our "anchor" here; it's the point at which our desired focal point and the corresponding background-position values are equal.

Let's pretend we want to focus on 700px or 87.5%. We go 100% of the way from the center: 50% + 50%.

See the Pen Background Focus: percentage (4th attempt) by Jay (@jsit) on CodePen.

With background-position set to 100%, the center of our bonding box has "panned" from the center of the image, 3/4 of the way to the rightmost edge (from 400px to 700px). If we want to "pan" to the rightmost edge, we need to go that extra 1/4, or 200px. 1/4 is 1/3 of 3/4, so we need to go a third more than we did a moment ago, or a total of 66.667% from the center:

See the Pen Background Focus: percentage (7th attempt) by Jay (@jsit) on CodePen.

Whew! So to focus on the rightmost edge of a background-image that is 4 times the size of our bounding box, we need to set background-position: 116.667% 0;.

How the heck are we supposed to figure that out?

It's a difference of 16.667% from the 100% we might expect. So if we wanted to focus on our original goal of 300px (or, 37.5%), we'd, uh, add 16.667%? There's no way this is going to work:

See the Pen Background Focus: percentage (8th attempt) by Jay (@jsit) on CodePen.

Nope.

If we wanted to focus on the leftmost edge, we'd probably subtract 16.667% from 0%, right? That sounds like it could be right.

See the Pen Background Focus: percentage (9th attempt) by Jay (@jsit) on CodePen.

Cool!

To focus at 100% or 0%, you have to "overshoot" those values by a certain amount, when measured from the center.

So if we want to focus on 0%, or "100% of the way to the left of the center," we have to subtract 66.667% from 50%. If we want to focus on 100%, or "100% of the way to the right of the center," we have to add 66.667% to 50%.

I might have expected to have to add or subtract 50% to or from 50% to get to those edges: a 1:1 ratio of "how far I want to go from the center" to "what my background-position value should be." But instead, we have to use a 4:3 ratio. In other words, we have to use a value four-thirds more "away from the center."

Things are getting a little hairy here, so let's introduce some terms:

  • c: Desired focal point (in percent) from leftmost edge of background image
  • z: Zoom factor (background width ÷ bounding box width)
  • p: background-position, to focus on c, given z

So we take a focal point's distance from the center (c − 50), multiply it by 4/3, then add that result to "the center," or 50.

If you wanted to focus on the point at 600px (or 75%), my background-position value should be:

(75% − 50%) × 4/3 + 50% = 83.333%

Yes, that sounds like it could work! Please please please:

See the Pen Background Focus: percentage (10th attempt) by Jay (@jsit) on CodePen.

Awesome!

And if you wanted to focus on 200px, or 25%, you would do:

(25% − 50%) × 4/3 + 50% = 16.667%

See the Pen Background Focus: percentage (11th attempt) by Jay (@jsit) on CodePen.

Wow.

Let's generalize this:

(c − 50%) × 4/3 + 50% = p

So why 4/3? 4 is the ratio of our background-image width to our bounding box width; and 3 is... 1 less than 4. Could it be that simple? Let's try a larger background-image, this time 1000px wide, or 5 times the width of our bounding box. And let's again try to focus on the point at 200px. Here our equation would be:

(20% − 50%) × 5/4 + 50% = 12.5%

See the Pen Background Focus: percentage (12th attempt) by Jay (@jsit) on CodePen.

Oh my god. It works!

So to revisit our equation, with a variable background-to-bounding-box ratio:

(c − 50%) × z/(z − 1) + 50% = p

Let's turn this into English:

Given a point on a background-image at location c...

  1. with c expressed as a percentage of the width of the image
  2. where c is intended to lie in the horizontal center of the background image's bounding box
  3. and where the background-image is z times as wide as its bounding box

the correct background-position p (expressed as a percentage) is given by the following formula:

(c − 50%) × z/(z − 1) + 50% = p

Can we generalize this even more?

What if we wanted to align the point at 25% of the background-image with the point at 75% of the bounding box? Yikes!

Let's revisit our original formula:

(c − 50%) × z/(z − 1) + 50% = p

Now let's introduce some new terms:

  • b: Desired focal point (in percent) from the leftmost edge of the bounding box. Earlier we had assumed this to always be 50% so that the center of the bounding box would be focused on our target in the background-image.
  • d: background-image focal point (in percent) to align to bounding box's midpoint in order to get c to align to b in the bounding box; if d of the background-image aligns with 50% of the bounding box, then c of the background-image will align with b of the bounding box.

Let's think of it this way

To want to align position c of a background-image with position b of a bounding box is to want to align some other position, d, of a background-image with the center of the bounding box - and we already know how to do that. So can we figure out a way to derive d, the spot we need to be at 50%, from c, b, and z? Sure!

With our 800px wide background-image, in a 200px-wide bounding box (z = 4), if we want to focus the rightmost edge of the bounding box (b = 100%) on the position at 600px (c = 75%) in the image, we would want the center of the bounding box to be focused on the point at 500px (d = 62.5%).

How do we get from c (75%) to d (62.5%)? Where does that -12.5% difference come from?

Well, our b was 100%, 50% greater than our old "default" b of 50%. And 12.5% is 1/4 of that; 1/4 is the inverse of our z of 4. Is that where our d comes from? That would be:

d = c + (50% - b)/z

Looks promising. Now we can substitute d in for c in the original formula:

(d − 50%) × z/(z − 1) + 50% = p

Or:

(c + (50% − b)/z - 50%) × z/(z − 1) + 50% = p

Whew! Let's test this. Let's try to align the position at 25% in our background-image (200px) with the position at 75% in our bounding box. This would be:

p = (25% + (50% - 75%)/4 - 50%) × 4/(4 - 1) + 50%
p = -31.25% × 1.333 + 50%
p = 8.333%

See the Pen Background Focus: percentage (13th attempt) by Jay (@jsit) on CodePen.

Unbelievable! Let's double check. How about the point at 87.5% in our background-image (700px) aligned with the position at 33.333% in our bounding box:

p = (87.5% + (50% - 33.333%)/4 - 50%) × 4/(4 - 1) + 50%
p = 41.6667% × 1.333 + 50%
p = 105.555%

See the Pen Background Focus: percentage (14th attempt) by Jay (@jsit) on CodePen.

Looks good enough to me!

I'm sure there is something intuitive about this to certain types of people, but I am not one of those people.

Let's build a Sass function that will do all this ridiculous math for us.

See the Pen Background Focus: percentage (final Sass function) by Jay (@jsit) on CodePen.

My head is spinning.

When I began tackling this problem I did not expect it to be this difficult, but what a journey. I hope guiding you through my thought process has been enlightening, and that you may at some point find value in our little Sass function.

All the Pens embedded in this article can be found in this collection.


Focusing a `background-image` on a Precise Location with Percentages is a post from CSS-Tricks

Read the whole story
grudes
8 days ago
reply
Share this story
Delete

Password Rules Are Bullshit

3 Comments and 19 Shares

Of the many, many, many bad things about passwords, you know what the worst is? Password rules.

Let this pledge be duly noted on the permanent record of the Internet. I don't know if there's an afterlife, but I'll be finding out soon enough, and I plan to go out mad as hell.

The world is absolutely awash in terrible password rules:

But I don't need to tell you this. The more likely you are to use a truly random password generation tool, like us über-geeks are supposed to, the more likely you have suffered mightily – and daily – under this regime.

Have you seen the classic XKCD about passwords?

To anyone who understands information theory and security and is in an infuriating argument with someone who does not (possibly involving mixed case), I sincerely apologize.

We can certainly debate whether "correct horse battery staple" is a viable password strategy or not, but the argument here is mostly that length matters.

That's What She Said

No, seriously, it does. I'll go so far as to say your password is too damn short. These days, given the state of cloud computing and GPU password hash cracking, any password of 8 characters or less is perilously close to no password at all.

So then perhaps we have one rule, that passwords must not be short. A long password is much more likely to be secure than a short one … right?

What about this four character password?

✅🐎🔋🖇️

What about this eight character password?

正确马电池订书钉

Or this (hypothetical, but all too real) seven character password?

You may also be surprised, if you paste the above four Unicode emojis into your favorite login dialog (go ahead – try it), to discover that it … isn't in fact four characters.

Oh dear.

"💩".length === 2

Our old pal Unicode strikes again.

As it turns out, even the simple rule that "your password must be of reasonable length" … ain't necessarily so. Particularly if we stop thinking like Ugly ASCII Americans.

And what of those nice, long passwords? Are they always secure?

aaaaaaaaaaaaaaaaaaa
0123456789012345689
passwordpassword
usernamepassword

Of course not, because have you met any users lately?

I changed all my passwords to

They consistently ruin every piece of software I've ever written. Yes, yes, I know you, Mr. or Ms. über-geek, know all about the concept of entropy. But expressing your love of entropy as terrible, idiosyncratic password rules …

  • must contain uppercase
  • must contain lowercase
  • must contain a number
  • must contain a special character

… is a spectacular failure of imagination in a world of Unicode and Emoji.

As we built Discourse, I discovered that the login dialog was a remarkably complex piece of software, despite its surface simplicity. The primary password rule we used was also the simplest one: length. Since I wrote that, we've already increased our minimum password default length from 8 to 10 characters. And if you happen to be an admin or moderator, we decided the minimum has to be even more, 15 characters.

I also advocated checking passwords against the 100,000 most common passwords. If you look at 10 million passwords from data breaches in 2016, you'll find the top 25 most used passwords are:

123456
123456789
qwerty
12345678
111111
1234567890
1234567
password
123123
987654321
qwertyuiop
mynoob
123321
666666
18atcskd2w
7777777
1q2w3e4r
654321
555555
3rjs1la7qe
google
1q2w3e4r5t
123qwe
zxcvbnm
1q2w3e

Even this data betrays some ASCII-centrism. The numbers are the same in any culture I suppose, but I find it hard to believe the average Chinese person will ever choose the passwords "password", "quertyuiop", or "mynoob". So this list has to be customizable, localizable.

(One interesting idea is to search for common shorter password matches inside longer passwords, but I think this would cause too many false positives.)

If you examine the data, this also turns into an argument in favor of password length. Note that only 5 of the top 25 passwords are 10 characters, so if we require 10 character passwords, we've already reduced our exposure to the most common passwords by 80%. I saw this originally when I gathered millions and millions of leaked passwords for Discourse research, then filtered the list down to just those passwords reflecting our new minimum requirement of 10 characters or more.

It suddenly became a tiny list. (If you've done similar common password research, please do share your results in the comments.)

I'd like to offer the following common sense advice to my fellow developers:

1. Password rules are bullshit

  • They don't work.
  • They heavily penalize your ideal audience, people that use real random password generators. Hey guess what, that password randomly didn't have a number or symbol in it. I just double checked my math textbook, and yep, it's possible. I'm pretty sure.
  • They frustrate average users, who then become uncooperative and use "creative" workarounds that make their passwords less secure.
  • They are often wrong, in the sense that the rules chosen are grossly incomplete and/or insane, per the many shaming links I've shared above.
  • Seriously, for the love of God, stop with this arbitrary password rule nonsense already. If you won't take my word for it, read this 2016 NIST password rules recommendation. It's right there, "no composition rules". However, I do see one error, it should have said "no bullshit composition rules".

2. Enforce a minimum Unicode password length

One rule is at least easy to remember, understand, and enforce. This is the proverbial one rule to bring them all, and in the darkness bind them.

  • It's simple. Users can count. Most of them, anyway.
  • It works. The data shows us it works; just download any common password list of your choice and group by password length.
  • The math doesn't lie. All other things being equal, a longer password will be more random – and thus more secure – than a short password.
  • Accept that even this one rule isn't inviolate. A minimum password length of 6 on a Chinese site might be perfectly reasonable. A 20 character password can be ridiculously insecure.
  • If you don't allow (almost) every single unicode character in the password input field, you are probably doing it wrong.
  • It's a bit of an implementation detail, but make sure maximum password length is reasonable as well.

3. Check for common passwords

As I've already noted, the definition of "common" depends on your audience, and language, but it is a terrible disservice to users when you let them choose passwords that exist in the list of 10k, 100k, or million most common known passwords from data breaches. There's no question that a hacker will submit these common passwords in a hack attempt – and it's shocking how far you can get, even with aggressive password attempt rate limiting, using just the 1,000 most common passwords.

  • 1.6% have a password from the top 10 passwords
  • 4.4% have a password from the top 100 passwords
  • 9.7% have a password from the top 500 passwords
  • 13.2% have a password from the top 1,000 passwords
  • 30% have a password from the top 10,000 passwords

Lucky you, there are millions and millions of real breached password lists out there to sift through. It is sort of fun to do data forensics, because these aren't hypothetical synthetic Jack the Ripper password rules some bored programmer dreamed up, these are real passwords used by real users.

Do the research. Collect the data. Protect your users from themselves.

4. Check for basic entropy

No need to get fancy here; pick the measure of entropy that satisfies you deep in the truthiness of your gut. But remember you have to be able to explain it to users when they fail the check, too.

entropy visualized

I had a bit of a sad when I realized that we were perfectly fine with users selecting a 10 character password that was literally "aaaaaaaaaa". In my opinion, the simplest way to do this is to ensure that there are at least (x) unique characters out of (y) total characters. And that's what we do as of the current beta version of Discourse. But I'd love your ideas in the comments, too. The simpler and clearer the better!

5. Check for special case passwords

I'm embarrassed to admit that when building the Discourse login, as I discussed in The God Login, we missed two common cases that you really have to block:

  • password equal to username
  • password equal to email address

🤦 If you are using Discourse versions earlier than 1.4, I'm so sorry and please upgrade immediately.

Similarly, you might also want to block other special cases like

  • password equal to URL or domain of website
  • password equal to app name

In short, try to think outside the password input box, like a user would.

🔔 Clarification

A few people have interpreted this post as "all the other password rules are bullshit, except these four I will now list." That's not what I'm trying to say here.

The idea is to focus on the one understandable, simple, practical, works-in-real-life-in-every-situation rule: length. Users can enter (almost) anything, in proper Unicode, provided it's long enough. That's the one rule to bind them all that we need to teach users: length!

Items #3 through #5 are more like genie-special-exception checks, a you can't wish for infinite wishes kind of thing. It doesn't need to be discussed up front because it should be really rare. Yes, you must stop users from having comically bad passwords that equal their username, or aaaaaaaaaaa or 0123456789, but only as post-entry checks, not as rules that need to be explained in advance.

So TL;DR: one rule. Length. Enter whatever you want, just make sure it's long enough to be a reasonable password.

[advertisement] Building out your tech team? Stack Overflow Careers helps you hire from the largest community for programmers on the planet. We built our site with developers like you in mind.
Read the whole story
grudes
22 days ago
reply
PhaChayFy
39 days ago
reply
Australia
popular
46 days ago
reply
GreenChange
41 days ago
At my previous employer, they used to give you a prize (just a lolly) when you first started, if you could pick a password that passed the stupid rules restrictions on the first try. Hardly anyone ever did it, even though the rules were listed clearly!
Share this story
Delete
3 public comments
chrisminett
45 days ago
reply
We need to check the last points (username, app name)
Milton Keynes, UK
wmorrell
46 days ago
reply
True story: work wants to roll out Microsoft Office 365, and I was one of the first trial users. I got a post-it with an 8 character password from the IT grunt tapped to be the AD admin. As is my habit, I immediately changed the password with a random one created by a password manager. The password was 20 characters. The change password form accepts the new password and prints a happy "password changed!" message. I log out, then try to log back in; the login page then informs me that the maximum … *maximum* password length is 16 characters and rejects my login. Okay … truncate it to 16, maybe the change form cut it off. Login fails. Go back to IT grunt to get a password reset, get a new 8-character password. Login fails. Reset again. Be very careful copying down password, very careful entering it back in. Login fails.
So, it turns out that there is no length validation on Office 365 password change forms, and going over the 16-character minimum mentioned nowhere on the page will *permanently* lock your account. 👍
expatpaul
46 days ago
Why is there even an upper limit? If the password is properly salted and hashed then only the hash should matter.
wmorrell
46 days ago
From what I found, it is some backward-compatible dependency thing with Active Directory syncing, which Microsoft has not cared enough about to fix. Possibly something with early Windows versions storing passwords as reversible hashes, and definitions of the protocols for remote logins defining a now-too-short field for passwords. The limitation could have made sense in the early 1990s, but then got carried forward far too long, and we are still stuck with it 25 years later.
expatpaul
46 days ago
Ah, I can see how that would happen. In my experience, many of the problems with Windows can be traced to poor early implementation that was never (or becomes increasingly difficult to) fix.
expatpaul
47 days ago
reply
Possibly the worst password rule is the one that demands you change your password on a regular basis. Either people will start writing down their passwords, or come up with a pattern that ensures their passwords are always easy to guess.
Belgium
wffurr
47 days ago
What's wrong with writing down passwords? A written copy is extremely useful, if you secure it the same way you do your money and credit cards, i.e. carry it in your pocket.
expatpaul
47 days ago
Point taken, wffurr. I was thinking more about the corporate environment which is where I usually see mad password rules like these. The number of times I have seen passwords on post-it notes, whichg are stuck somewhere convenient, is quite frightening.
expatpaul
47 days ago
That said, the best approach is to use a password manager to store randomly generated passwords. Of course, my current employer bans the use of password mangers.
HarlandCorbin
46 days ago
Must change password every 21 days. Cannot reuse last 50 passwords. **These** rules make my passwords less secure than they could be. I have given up generating passwords that I can reasonably type that follow the rules. I mean, 21 days?!?
expatpaul
46 days ago
21 days? Ouch! The worst I saw was every 30 days, and I know a number of people using a combination on month and year for their password.
HarlandCorbin
46 days ago
And the new password can only have (IIRC) one point of similarity with the previous one.
expatpaul
46 days ago
That's just painful. It's rules like that which are just asking everyone to write their password on the nearest available post-it note.
Aatch
46 days ago
That's weirdly strict. We have a change every 90 days and you can't use your last 2 passwords. That's it. Simple enough to rotate a handful of passwords 4 times a year.
chrisrosa
46 days ago
this one drives me crazy. the damn auditors eat password expiry up and are always pushing for less time. total bs.
mareino
46 days ago
There is a government personnel website I've used where (1) the average user logs in about 2x/year, (2) the password resets monthly.
WorldMaker
42 days ago
The NIST guidelines link in the post also strongly recommend against arbitrary password expiration. I sent the NIST document to my corporate IT when they changed password expiration rules just recently. It hasn't impacted any change, but at least I tried to talk sense to power.
expatpaul
41 days ago
@WorldMaker: I'm impressed that you tried to talk sense, but the main problem with large corporations is that they tend to adopt a checkbox approach to these things. People have to prove that they are doing _something_ about security; no-one ever asks whether what they are doing is actually useful.
WorldMaker
40 days ago
@expatpaul: Arguably as a software developer a part of my role is to evaluate and better the company's software. Even if that just means writing a ticket every few weeks to try to argue true industry best practices against fads and security theater. Of course, without a CTO title they don't have to listen to me, but I can hope they might at least read it. Even if they are hearing stupid crap from outside security consultants and terrible software vendors that should be destroyed for the betterment of the corporate world like Oracle. The only way we might see change is to keep talking sense to power and hope someone listens or promotes us until they have to listen.
chrisrosa
40 days ago
As long as companies want to do business with companies the require SOC2, HIPPA, SOX, etc. (not to mention their own compliance BS), it doesn't really matter. At least NIST is on board.

`matrix3d()` for a Frame-Perfect Custom Scrollbar

2 Shares

Das Surma:

In this article we will leverage some unconventional CSS matrices to build a custom scroller that doesn't require any JavaScript while scrolling, just some setup code.

If turning a scrollbar into a Nyan cat with near-perfect functionality isn't a CSS trick, I don't know what is.

Direct Link to ArticlePermalink


`matrix3d()` for a Frame-Perfect Custom Scrollbar is a post from CSS-Tricks

Read the whole story
PhaChayFy
18 days ago
reply
Australia
grudes
22 days ago
reply
Share this story
Delete

This is not a test: Google Optimize now free — for everyone

1 Share
Businesses often have one big question for us: How can they better understand their website visitors and deliver more relevant, engaging experiences?

To help businesses test and take action, last spring we launched our enterprise-class A/B testing and personalization product, Google Optimize 360. We saw great demand, so we made it more accessible with a free beta version last fall — and that response also exceeded our expectations, with over 250,000 users requesting an Optimize account.

Today we're very excited to announce that both Optimize and Optimize 360 are now out of beta. And Optimize is now immediately available to everyone — for free. This is not a test: You can start using it today.

Easy to implement 

A recent survey showed 45% of small and medium businesses don’t optimize their websites through A/B testing.1 The two most common reasons given were a "lack of employee resources" and "lack of knowledge to get started."

If you're part of that 45%, Optimize is a great choice for you. Optimize has many of the same features as Optimize 360. It's just right for small and medium-sized businesses who need powerful testing, but don't have the budget or team resources for an enterprise-level solution. Optimize is easy for anyone to set up. Early users of Optimize have been happy with how easy it is to use. In fact, it's built right on top of Analytics, so if you're already an Analytics user you'll add just a single line of code to get Optimize up and running. With just a few clicks more, you can start using your Analytics data to design experiments and improve the online experience for your users.

Easy to use

Worried about having to hire someone to run A/B tests on your site, or frustrated about not knowing how to do it yourself? Don't be. The Optimize visual editor allows for WYSIWYG (what-you-see-is-what-you-get) editing so you can change just about anything on your site with a drag and a drop. And more advanced users will enjoy the ability to edit raw HTML or add JavaScript or CSS rules directly in the editor.


Powerful targeting capabilities within Optimize allow you to serve the right experiences to just the right set of users. And you have flexible URL targeting capabilities to create simple or complex rules for the pages where you want your experiment to run. To find out if a targeting rule you've set will apply to a specific URL on your site, use the new Optimize URL tester. Just enter a URL and the tester will immediately tell you if that page is a match for your targeting rule.

Easy to understand

Optimize calculates results based on your existing Analytics metrics and objectives using advanced Bayesian methods, so the reporting shows you exactly what you need to know to make better and faster decisions.


We’ve also upgraded the improvement overview (see image above) to help you quickly see how an experiment affects the metrics you care about most, whether that means purchases, pageviews, session lengths, or whatever else you’re tracking in Analytics.

Easy to try 

Leading businesses are building a culture of growth that embraces the use of data and testing to improve the customer experience every day. We’re delighted to offer Optimize to everyone to help deliver better user experiences across the board.

As of today, Optimize is available in over 180 countries. (A special note for our European users: We’ve added a new data processing amendment to the Google Optimize Terms of Service that you may review in the UI and accept if you wish.) And we're not done yet: Keep an eye out for more improvements and announcements in the future.

What are you waiting for? Try it right now!

Happy Optimizing!

1Google Surveys, "Website Optimization Challenges for SMBs," Base: 506 Small/Medium Business Owners and Managers, Google Surveys Audience Panel, U.S., March 2017

Read the whole story
grudes
22 days ago
reply
Share this story
Delete

CSS deep-dive: matrix3d() for a frame-perfect custom scrollbar

2 Shares

CSS deep-dive: matrix3d() for a frame-perfect custom scrollbar

Custom scrollbars are extremely rare and that’s mostly due to the fact that scrollbars are one of the remaining bits on the web that are pretty much unstylable (I’m looking at you, date picker). You can use JavaScript to build your own, but that’s expensive, low fidelity and can feel laggy. In this article we will leverage some unconventional CSS matrices to build a custom scroller that doesn’t require any JavaScript while scrolling, just some setup code.

TL;DR:

You don’t care about the nitty gritty? You just want to look at the Nyan cat demo and get the library? You can find the demo’s code in our GitHub repo.

LAM;WRA (Long and mathematical; will read anyways):

Note: This article does some weird stuff with homogeneous coordinates as well as matrix calculations. It is good to have some basic understanding of these concepts if you want to understand the intricate details of this trick. However, we hope that – even if you don’t enjoy matrix math – you can still follow along and see how we used them.

A while ago we built a parallax scroller (Did you read that article? It’s really good, well worth your time!). By pushing elements back using CSS 3D transforms, elements moved slower than our actual scrolling speed.

Recap

Let’s start off with a recap of how the parallax scroller worked.

As shown in the animation, we achieved the parallax effect by pushing elements “backwards” in 3D space, along the Z axis. Scrolling a document is effectively a translation along the Y axis. So if we scroll down by, say 100px, every element will be translated upwards by 100px. That applies to all elements, even the ones that are “further back”. But because they are farther away from the camera their observed on-screen movement will be less than 100px, yielding the desired parallax effect.

Of course, moving an element back in space will also make it appear smaller, which we correct by scaling the element back up. We figured out the exact math when we built the parallax scroller, so I won’t repeat all the details.

Step 0: What do we wanna do?

Scrollbars. That’s what we are going to build. But have you ever really thought about what they do? I certainly didn’t. Scrollbars are an indicator of how much of the available content is currently visible and how much progress you as the reader have made. If you scroll down, so does the scrollbar to indicate that you are making progress towards the end. If all the content fits into the viewport the scrollbar is usually hidden. If the content has 2x the height of the viewport, the scrollbar fills ½ of the height of the viewport. Content worth 3x the height of the viewport scales the scrollbar to ⅓ of the viewport etc. You see the pattern. Instead of scrolling you can also click-and-drag the scrollbar to move through the site faster. That’s a surprising amount of behavior for an inconspicuous element like that. Let’s fight one battle at a time.

Step 1: Putting it in reverse

Okay, we can make elements move slower than the scrolling speed with CSS 3D transforms as outlined in the parallax scrolling article. Can we also reverse the direction? It turns out we can and that’s our way in for building a frame-perfect, custom scrollbar. To understand how this works we need to cover a few CSS 3D basics first.

To get any kind of perspective projection in the mathematical sense, you’ll most likely end up using homogeneous coordinates. I’m not going into detail what they are and why they work, but you can think of them like 3D coordinates with an additional, fourth coordinate called w. This coordinate should be 1 except if you want to have perspective distortion. We don’t need to worry about the detials of w as we are not going to use any other value than 1. Therefore all points are from now on 4-dimensional vectors [x, y, z, w=1] and consequently matrices need to be 4x4 as well.

One occasion where you can see that CSS uses homogeneous coordinates under the hood is when you define your own 4x4 matrices in a transform property using the matrix3d() function. matrix3d takes 16 arguments (because the matrix is 4x4), specifying one column after the other. So we can use this function to manually specify rotations, translations etc.. But what it also allows us to do is mess around with that w coordinate!

Before we can make use of matrix3d(), we need a 3D context – because without a 3D context there wouldn’t be any perspective distortion and no need for homogeneous coordinates. To create a 3D context we need a container with a perspective and some elements inside that we can transform in the newly created 3D space. For example:

A piece of CSS code that distorts a div using CSS’
    perspective attribute.

The elements inside a perspective container are processed by the CSS engine as follows:

  • Turn each corner (vertex) of an element into homogenous coordinates [x,y,z,w], relative to the perspective container.
  • Apply all of the element’s transforms as matrices from right to left.
  • If the perspective element is scrollable, apply a scroll matrix.
  • Apply the perspective matrix.

The scroll matrix is a translation along the y axis. If we scroll down by 400px, all elements need to be moved up by 400px. The perspective matrix is a matrix that “pulls” points closer to the vanishing point the further back in 3D space they are. This achieves both effects of making things appear smaller when they are farther back and also makes them “move slower” when being translated. So if an element is pushed back, a translation of 400px will cause the element to only move 300px on the screen.

If you want to know all the details, you should read the spec on CSS’ transform rendering model, but for the sake of this article I simplified the algorithm above.

Our box is inside a perspective container with value p for the perspective attribute, and let’s assume the container is scrollable and is scrolled down by n pixels.

Perspective matrix times scroll matrix times element transform matrix
  equals four by four identity matrix with minus one over p in the fourth row
  third column times four by four identity matrix with minus n in the second
  row fourth column times element transform matrix.

The first matrix is the perspective matrix, the second matrix is the scroll matrix. To recap: The scroll matrix’ job is to make an element move up when we are scrolling down, hence the negative sign.

For our scrollbar however we want the opposite – we want our element to move down when we are scrolling down. Here’s where we can use a trick: Inverting the w coordinate of the corners of our box. If the w coordinate is -1, all translations will take effect in the opposite direction. So how do we do that? The CSS engine takes care of converting the corners of our box to homogeneous coordinates, and sets w to 1. It’s time for matrix3d() to shine!

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    );
}

This matrix will do nothing else then negating w. So when the CSS engine has turned each corner into a vector of the form [x,y,z,1], the matrix will convert it into [x,y,z,-1].

Four by four identity matrix with minus one over p in the fourth row
  third column times four by four identity matrix with minus n in the second
  row fourth column times four by four identity matrix with minus one in the
  fourth row fourth column times four dimensional vector x, y, z, 1 equals four
  by four identity matrix with minus one over p in the fourth row third column,
  minus n in the second row fourth column and minus one in the fourth row
  fourth column equals four dimensional vector x, y plus n, z, minus z over
  p minus 1.

I listed an intermediate step to show the effect of our element transform matrix. If you are not comfortable with matrix math, that is okay. The Eureka moment is that in the last line we end up adding the scroll offset n to our y coordinate instead of subtracting it. The element will be translated downwards if we scroll down.

However, if we just put this matrix in our example, the element will not be displayed. This is because the CSS spec requires that any vertex with w < 0 blocks the element from being rendered. And since our z coordinate is currently 0, and p is 1, w will be -1.

Luckily, we can choose the value of z! To make sure we end up with w=1, we need to set z = -2.

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    )
    translateZ(-2px);
}

Lo and behold, our box is back!

Step 2: Make it move

Now our box is there and is looking the same way it would have without any transforms. Right now the perspective container is not scrollable, so we can’t see it, but we know that our element will go the other direction when scrolled. So let’s make the container scroll, shall we? We can just add a spacer element that takes up space:

<div class="container">
  <div class="box"></div>
  <span class="spacer"></span>
</div>

<style>
/* … all the styles from the previous example … */
.container {
  overflow: scroll;
}
.spacer {
  display: block;
  height: 500px;
}
</style>

And now scroll the box! The red box moves down.

Step 3: Give it a size

We have an element that moves down when the page scrolls down. That’s the hard bit out of the way, really. Now we need to style it to look like a scrollbar and make it a bit more interactive.

A scrollbar usually consists of a “thumb” and a “track”, while the track isn’t always visible. The thumb’s height is directly proportional to how much of the content is visible.

<script>
  const scroller = document.querySelector('.container');
  const thumb = document.querySelector('.box');
  const scrollerHeight = scroller.getBoundingClientRect().height;
  thumb.style.height = /* ??? */;
</script>

scrollerHeight is the height of the scrollable element, while scroller.scrollHeight is the total height of the scrollable content. scrollerHeight/scroller.scrollHeight is the fraction of the content that is visible. The ratio of vertical space the thumb covers should be equal to the ratio of content that is visible:

thumb dot style dot height over scrollerHeight equals scroller height
  over scroller dot scroll height if and only if thumb dot style dot height
  equals scroller height times scroller height over scroller dot scroll
  height.

<script>
  // …
  thumb.style.height =
    scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
  // Accommodate for native scrollbars
  thumb.style.right =
    (scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>

Note: We need to adjust right so our custom scrollbar is visible on systems with permanent native scrollbars. We will completely hide the native scrollbars later with a trick.

The size of the thumb is looking good, but it’s moving way too fast. This is where we can grab our technique from the parallax scroller. If we move the element further back it will move slower while scrolling. We can correct the size by scaling it up. But how much should we push it back exactly? Let’s do some – you guessed it – math! This is the last time, I promise.

The crucial bit of information is that we want the bottom edge of the thumb to line up with the bottom edge of the scrollable element when scrolled all the way down. In other words: If we have scrolled scroller.scrollHeight - scroller.height pixels, we want our thumb to be translated by scroller.height - thumb.height. For every pixel of scroller, we want our thumb to move a fraction of a pixel:

factor equals scroller dot height minus thumb dot height over scroller
  dot scroll height minus scroller dot height.

That’s our scaling factor. Now we need to convert the scaling factor into a translation along the z axis, which we already did in the parallax scrolling article. According to the relevant section in the spec: The scaling factor is equal to p/(p − z). We can solve this equation for z to figure out how much we we need to translate our thumb along the z axis. But keep in mind that due to our w coordinate shenanigans we need to translate an additional -2px along z. Also note that an element’s transforms are applied right to left, meaning that all translations before our special matrix will not be inverted, all translations after our special matrix, however, will! Let’s codify this!

<script>
  // ... code from above...
  const factor =
    (scrollerHeight - thumbHeight)/(scroller.scrollHeight - scrollerHeight);
  thumb.style.transform = `
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    )
    scale(${1/factor})
    translateZ(${1 - 1/factor}px)
    translateZ(-2px)
  `;
</script>

We have a scrollbar! And it’s just a DOM element that we can style however we like. One thing that is important to do in terms of accessibility is to make the thumb respond to click-and-drag, as many users are used to interacting with a scrollbar that way. For the sake of not making this blog post even longer, I am not going explain the details for that part. Take a look at the library code for details if you want to see how it’s done.

What about iOS?

Ah, my old friend iOS Safari. As with the parallax scrolling, we run into an issue here. Because we are scrolling on an element, we need to specify -webkit-overflow-scrolling: touch, but that causes 3D flattening and our entire scrolling effect stops working. We solved this problem in the parallax scroller by detecting iOS Safari and relying on position: sticky as a workaround, and we’ll do exactly the same thing here. Take a look at the parallaxing article to refresh your memory.

What about the browser scrollbar?

On some systems we will have to deal with a permanent, native scrollbar. Historically, the scrollbar can’t be hidden (except with a non-standard pseudo-selector). So to hide it we have to resort to some (math-free) hackery. We wrap our scrolling element in a container with overflow-x: hidden and make the scrolling element wider than the container. The browser’s native scrollbar is now out of view.

Fin

Putting it all together, we can now build a frame-perfect custom scrollbar – like the one in our Nyan cat demo.

If you can’t see Nyan cat, you are experiencing a bug that we found and filed while building this demo (click the thumb to make Nyan cat appear). Chrome is really good at avoiding unnecessary work like painting or animating things that are off-screen. The bad news is that our matrix shenanigans make Chrome think the Nyan cat gif is actually off-screen. Hopefully this will get fixed soon.

There you have it. That was a lot of work. I applaud you for reading the entire thing. This is some real trickery to get this working and it’s probably rarely worth the effort, except when a customized scrollbar is an essential part of the experience. But good to know that it is possible, no? The fact that it is this hard to do a custom scrollbar shows that there’s work to be done on CSS’s side. But fear not! In the future, Houdini’s AnimationWorklet is going to make frame-perfect scroll-linked effects like this a lot easier.

Read the whole story
grudes
22 days ago
reply
PhaChayFy
24 days ago
reply
Australia
Share this story
Delete

Building performant expand & collapse animations

1 Share

Building performant expand & collapse animations

TL;DR

Use scale transforms when animating clips. You can prevent the children from being stretched and skewed during the animation by counter-scaling them.

Previously we’ve posted updates on how to create performant parallax effects and infinite scrollers. In this post we’re going to look over what’s involved if you want performant clip animations. If you want to see a demo, check out the Sample UI Elements GitHub repo.

Take, for example, an expanding menu:

Some options for building this are more performant than others.

Bad: Animating width and height on a container element

You could imagine using a bit of CSS to animate the width and height on the container element.

.menu {
  overflow: hidden;
  width: 350px;
  height: 600px;
  transition: width 600ms ease-out, height 600ms ease-out;
}

.menu--collapsed {
  width: 200px;
  height: 60px;
}

The immediate problem with this approach is that it requires animating width and height. These properties require calculating layout and paint the results on every frame of the animation, which can be very expensive, and will typically cause you to miss out on 60fps. If that’s news to you then read our Rendering Performance guides, where you can get more information on how the rendering process works.

Bad: Use the CSS clip or clip-path properties.

An alternative to animating width and height might be to use the (now-deprecated) clip property to animate the expand and collapse effect. Or, if you prefer, you could use clip-path instead. Using clip-path, however, is less well supported than clip. But clip is deprecated. Right. But don’t despair, this isn’t the solution you wanted anyway!

.menu {
  position: absolute;
  clip: rect(0px 112px 175px 0px);
  transition: clip 600ms ease-out;
}

.menu--collapsed {
  clip: rect(0px 70px 34px 0px);
}

While better than animating the width and height of the menu element, the downside of this approach is that it still triggers paint. Also the clip property, should you go that route, requires that the element it’s operating on is either absolutely or fixed positioned, which can require a little extra wrangling.

Good: animating scales

Since this effect involves something getting bigger and smaller, you can use a scale transform. This is great news because changing transforms is something that doesn’t require layout or paint, and which the browser can hand off to the GPU, meaning that the effect is accelerated and significantly more likely to hit 60fps.

The downside to this approach, like most things in rendering performance, is that it requires a bit of setting up. It’s totally worth it, though!

Step 1: Calculate the start and end states

With an approach that uses scale animations, the first step is to read elements that tell you the size the menu needs to be both when it’s collapsed, and when it’s expanded. It may be that for some situations you can’t get both of these bits of information in one go, and that you need to — say — toggle some classes around to be able to read the various states of the component. If you need to do that, however, be cautious: getBoundingClientRect() (or offsetWidth and offsetHeight) forces the browser to run styles and layout passes if styles have changed since they were last run.

function calculateCollapsedScale () {
  // The menu title can act as the marker for the collapsed state.
  const collapsed = menuTitle.getBoundingClientRect();

  // Whereas the menu as a whole (title plus items) can act as
  // a proxy for the expanded state.
  const expanded = menu.getBoundingClientRect();
  return {
    x: collapsed.width / expanded.width,
    y: collapsed.height / expanded.height
  }
}

In the case of something like a menu, we can make the reasonable assumption that it will start out being its natural scale (1, 1). This natural scale represents its expanded state meaning you will need to animate from a scaled down version (which was calculated above) back up to that natural scale.

But wait! Surely this would scale the contents of the menu as well, wouldn’t it? Well, as you can see below, yes.

So what can you do about this? Well you can apply a counter-transform to the contents, so for example if the container is scaled down to 1/5th of its normal size, you can scale the contents up by 5x to prevent the contents being squashed. There are two things to notice about that:

  1. The counter-transform is also a scale operation. This is good because it can also be accelerated, just like the animation on the container. You may need to ensure that the elements being animated get their own compositor layer (enabling the GPU to help out), and for that you can add will-change: transform to the element or, if you need to support older browsers, backface-visiblity: hidden.

  2. The counter-transform must be calculated per frame. This is where things can get a little trickier, because assuming that the animation is in CSS and uses an easing function, the easing itself need to be countered when animating the counter-transform. However, calculating the inverse curve for — say — cubic-bezier(0, 0, 0.3, 1) isn’t all that obvious.

It may be tempting, then, to consider animating the effect using JavaScript. After all, you could then use an easing equation to calculate the scale and counter-scale values per frame. The downside of any JavaScript-based animation is what happens when the main thread (where your JavaScript runs) is busy with some other task. The short answer is that your animation can stutter or halt altogether, which isn’t great for UX.

Step 2: Build CSS Animations on the fly

The solution, which may appear odd at first, is to create a keyframed animation with our own easing function dynamically and inject it into the page for use by the menu. (Big thanks to Chrome engineer Robert Flack for pointing this out!) The primary benefit of this is that a keyframed animation that mutates transforms can be run on the compositor, meaning that it isn’t affected by tasks on the main thread.

To make the keyframe animation we step from 0 to 100 and calculate what scale values would be needed for the element and its contents. These can then be boiled down to a string, which can be injected into the page as a style element. Injecting the styles will cause a Recalculate Styles pass on the page, which is additional work that the browser has to do, but it will do it only once when the component is booting up.

function createKeyframeAnimation () {
  // Figure out the size of the element when collapsed.
  let {x, y} = calculateCollapsedScale();
  let animation = '';
  let inverseAnimation = '';

  for (let step = 0; step <= 100; step++) {
    // Remap the step value to an eased one.
    let easedStep = ease(step / 100);

    // Calculate the scale of the element.
    const xScale = x + (1 - x) * easedStep;
    const yScale = y + (1 - y) * easedStep;

    animation += `${step}% {
      transform: scale(${xScale}, ${yScale});
    }`;

    // And now the inverse for the contents.
    const invXScale = 1 / xScale;
    const invYScale = 1 / yScale;
    inverseAnimation += `${step}% {
      transform: scale(${invXScale}, ${invYScale});
    }`;

  }

  return `
  @keyframes menuAnimation {
    ${animation}
  }

  @keyframes menuContentsAnimation {
    ${inverseAnimation}
  }`;
}

The endlessly curious may be wondering about the ease() function inside the for-loop. You can use something like this to map values from 0 to 1 to an eased equivalent.

function ease (v, pow=4) {
  return 1 - Math.pow(1 - v, pow);
}

You can use Google search to plot what that looks like as well. Handy! If you’re in need of other easing equations do check out Tween.js by Soledad Penadés, which contains a whole heap of them.

Step 3: Enable the CSS Animations

With these animations created and baked out to the page in JavaScript, the final step is to toggle classes enabling the animations.

.menu--expanded {
  animation-name: menuAnimation;
  animation-duration: 0.2s;
  animation-timing-function: linear;
}

.menu__contents--expanded {
  animation-name: menuContentsAnimation;
  animation-duration: 0.2s;
  animation-timing-function: linear;
}

This causes the animations to run that were created in the previous step. Because the baked animations are already eased, the timing function needs to be set to linear otherwise you’ll ease between each keyframe which will look very weird!

When it comes to collapsing the element back down there are two options: update the CSS animation to run in reverse rather than forwards. This will work just fine, but the "feel" of the animation will be reversed, so if you used an ease-out curve the reverse will feel eased in, which will make it feel sluggish. A more appropriate solution is to create a second pair of animations for collapsing the element. These can be created in exactly the same way as the expand keyframe animations, but with swapped start and end values.

const xScale = 1 + (x - 1) * easedStep;
const yScale = 1 + (y - 1) * easedStep;

A more advanced version: circular reveals

It’s also possible to use this technique to make circular expand and collapse animations.

The principles are largely the same as the previous version, where you scale an element, and counter-scale its immediate children. In this case the element that’s scaling up has a border-radius of 50%, making it circular, and is wrapped by another element that has overflow: hidden, meaning that you don’t see the circle expand outside of the element bounds.

A word of warning on this particular variant: Chrome has blurry text on low DPI screens during the animation because of rounding errors due to the scale and counter-scale of the text. If you’re interested in the details for that there’s a bug filed that you can star and follow.

The code for the circular expand effect can be found in the GitHub repo.

Conclusions

So there you have it, a way to do performant clip animations using scale transforms. In a perfect world it would be great to see clip animations be accelerated (there’s a Chromium bug for that made by Jake Archibald), but until we get there you should be cautious when animating clip or clip-path, and definitely avoid animating width or height.

It would also be handy to use Web Animations for effects like this, because they have a JavaScript API but can run on the compositor thread if you only animate transform and opacity. Unfortunately support for Web Animations isn’t great, though you could use progressive enhancement to use them if they’re available.

if ('animate' in HTMLElement.prototype) {
  // Animate with Web Animations.
} else {
  // Fall back to generated CSS Animations or JS.
}

Until that changes, while you can use JavaScript-based libraries to do the animation, you might find that you get more reliable performance by baking a CSS animation and using that instead. Equally, if your app already relies on JavaScript for its animations you may be better served by being at least consistent with your existing codebase.

If you want to have a look through the code for this effect take a look at the UI Element Samples Github repo and, as always, let us know how you get on in the comments below.

Read the whole story
grudes
22 days ago
reply
Share this story
Delete
Next Page of Stories