Tag: Level

Can JavaScript Detect the Browser’s Zoom Level?

No, not really.

My first guess was that this was intentionally not exposed in browsers because browsers intentionally don’t want us fighting it — or making well-intentioned but bad-outcome decisions based on that info. But I don’t see any evidence of that.

StackOverflow answers paint how weird cross-browser it can be. This script from 2013 works for me in Chrome, but not at all in Safari and reports incorrectly in Firefox. Even if that script worked, it relies on user agent detection (which is not long for this world) and some incredibly weird hacks.

So please, feel free to correct me if I’m wrong, but I think the answer is that we can’t really do this right now.

There is a thing called the Visual Viewport API

I’m kinda confused by it.

  • The spec is a draft
  • The support chart lists a lot of support
  • window.visualViewport is defined in Firefox, Safari, and Chrome (desktop)
  • But… window.visualViewport.scale is always 1 in Safari and Chrome, and undefined in Firefox. In other words: useless.

I don’t entirely know if that is supposed to accurately represent the browser zoom level, because the spec talks about specifically about pinch-zoom. So, maybe it’s just not intended for desktop browser zoom levels.

What’s a use case here?

I had a fella spell out a situation like this:

He wanted to use CSS grid to layout cemetery plots (interesting already), like a top-down blueprint of a graveyard. There was lots of information in the layout. If you were “zoomed out” so you could see the whole graveyard on one page, the text in each area would be too small to read (sincethe type would be sized to fit within the boxes/graves). Ideally, the page would hide that text while the browser is zoomed out (perhaps a .hide-text class). When zoomed in far enough, the text is shown again.

Like…

// Dunno if "resize" is best. I don't know what the "change zoom" event would be window.visualViewport.addEventListener("resize", viewportHandler); function viewportHandler(event) {   // NOTE: This doesn't actually work at time of writing   if (event.target.scale > 3) {     document.body.classList.remove("hide-text");     } else {     document.body.classList.add("hide-text");   } } 

There is Pixel Density…

Ben Nadel recently blogged: Looking At How Browser Zoom Affects CSS Media Queries And Pixel-Density.

If you look at window.devicePixelRatio and zoom in, the pixel density in Chrome and Firefox will increase as you zoom in and decrease as you zoom out. Theoretically, you could test the original value (it might start at different places for users with different screens) and use changes in that value to guess zoom. But… in Safari it does nothing, as in, it stays the same regardless of zoom. Plus, the operating system zoom level can affect things here, making it extra tricky; not to mention that a page might start at a zoomed in level which could throw off the whole calculation from the start.

The post Can JavaScript Detect the Browser’s Zoom Level? appeared first on CSS-Tricks.

CSS-Tricks

, , , ,

Level up your .sort game

Sorting is a super handy JavaScript method that can display the values of an array in a certain order. Whether that’s real estate listings by price, burger joints by distance, or best nearby happy hours by rating, sorting arrays of information is a common need.

If you’re already doing this with JavaScript on a project, you are will likely using the built-in array .sort method, which is in the same family of array methods that includes .filter, .map and .reduce.

Let’s take a look at how to do that!

A quick note about side effects

Before going into the details of how to use .sort, there is a very important detail that needs to be addressed. While many of the ES5 array methods such as .filter, .map, and .reduce will return a new array and leave the original untouched, .sort will sort the array in place. If this is unwanted, an ES6 technique to avoid this is using the spread operator to concisely create a new array.

const foo = ['c','b','a']; const bar = ['x','z','y']; const fooSorted = foo.sort(); const barSorted = [...bar].sort();  console.log({foo, fooSorted, bar, barSorted});  /* {   "foo":       [ "a", "b", "c" ],   "fooSorted": [ "a", "b", "c" ],   "bar":       [ "x", "z", "y" ],   "barSorted": [ "x", "y", "z" ] } */

foo and fooSorted both reference the same array, but bar and barSorted are now individual arrays.

General overview

The only parameter of the .sort method is a function. The spec refers to this as the compareFn — I will refer to it as the “comparison function” for the rest of the post. This comparison function accepts two parameters, which I will refer to as a and b. a and b are the two elements that we will be comparing. If you do not provide a comparison function, the array will coerce each element into a string and sort according to Unicode points.

If you would like the a to be ordered first in the array, the comparison function should return a negative integer; for b, a positive integer. If you would like the two to maintain their current order, return a 0.

If you don’t understand, don’t worry! Hopefully it will become much more clear with a few examples.

Comparing numbers

One of the simplest callbacks to write is a number comparison.

const numbers = [13,8,2,21,5,1,3,1]; const byValue = (a,b) => a - b; const sorted = [...numbers].sort(byValue); console.log(sorted); // [1,1,2,3,5,8,13,21]

If a is greater than b, a - b will return a positive number, so b will be sorted first.

Comparing strings

When comparing strings, the > and < operators will compare values based on each string’s Unicode value. This means that all uppercase letters will be “less” than all lowercase letters, which can lead to unexpected behavior.

JavaScript does have a method to help with comparing strings: the String.prototype.localeCompare method. This method accepts a comparison string, a locale, and an options object. The options object accepts a few properties (all of which you can view here), but I find that the most useful is “sensitivity.” This will affect how comparisons work between letter variations such as case and accent.

const strings = ['Über', 'alpha', 'Zeal', 'über', 'uber', 'Uber', 'Alpha', 'zeal'];  const sortBySensitivity = sensitivity => (a, b) => a.localeCompare(   b,   undefined, // locale string -- undefined means to use browser default   { sensitivity } );  const byAccent  = sortBySensitivity('accent'); const byBase    = sortBySensitivity('base'); const byCase    = sortBySensitivity('case'); const byVariant = sortBySensitivity('variant'); // default  const accentSorted  = [...strings].sort(byAccent); const baseSorted    = [...strings].sort(byBase); const caseSorted    = [...strings].sort(byCase); const variantSorted = [...strings].sort(byVariant);  console.log({accentSorted, baseSorted, caseSorted, variantSorted});  /* {   "accentSorted":  [ "alpha", "Alpha", "uber", "Uber", "Über", "über", "Zeal", "zeal" ],   "baseSorted":    [ "alpha", "Alpha", "Über", "über", "uber", "Uber", "Zeal", "zeal" ],   "caseSorted":    [ "alpha", "Alpha", "über", "uber", "Über", "Uber", "zeal", "Zeal" ],   "variantSorted": [ "alpha", "Alpha", "uber", "Uber", "über", "Über", "zeal", "Zeal" ] } */

To me, baseSorted seems to be the most logical for most alphabetical sorting — ‘ü’, ‘u’, ‘Ü’, and ‘U’ are equivalent, so they remain in the order of the original array.

Running functions before comparing values

You may want to run a comparison function on a value that is derived from each array’s element. First, let’s write a comparison function factory that will “map” over the element before calling the comparison function.

const sortByMapped = (map,compareFn) => (a,b) => compareFn(map(a),map(b));

One use case for this is sorting based on the attribute of an object.

const purchases = [   { name: 'Popcorn', price: 5.75 },    { name: 'Movie Ticket', price: 12 },   { name: 'Soda', price: 3.75 },   { name: 'Candy', price: 5 }, ];  const sortByMapped = (map,compareFn) => (a,b) => compareFn(map(a),map(b)); const byValue = (a,b) => a - b; const toPrice = e => e.price; const byPrice = sortByMapped(toPrice,byValue);  console.log([...purchases].sort(byPrice));  /* [   { name: "Soda", price: 3.75 },   { name: "Candy", price: 5 },   { name: "Popcorn", price: 5.75 },   { name: "Movie Ticket", price: 12 } ] */

Another case might be to compare an array of dates.

const dates  = ['2018-12-10', '1991-02-10', '2015-10-07', '1990-01-11']; const sortByMapped = (map,compareFn) => (a,b) => compareFn(map(a),map(b)); const toDate = e => new Date(e).getTime(); const byValue = (a,b) => a - b; const byDate = sortByMapped(toDate,byValue);  console.log([...dates].sort(byDate)); // ["1990-01-11", "1991-02-10", "2015-10-07", "2018-12-10"]

Reversing a sort

There are some cases where you may want to reverse the outcome of a comparison function. This is subtly different than doing a sort and then reversing the result in the way ties are handled: if you reverse the outcome, ties will also be reversed in order.

To write a higher order function that accepts a comparison function and returns a new one, you will need to flip the sign of the comparison’s return value.

const flipComparison = fn => (a,b) => -fn(a,b); const byAlpha = (a,b) => a.localeCompare(b, null, { sensitivity: 'base' }); const byReverseAlpha = flipComparison(byAlpha);  console.log(['A', 'B', 'C'].sort(byReverseAlpha)); // ['C','B','A']

Running a tiebreaker sort

There are times when you may want to have a “tie-breaker” sort — that is, another comparison function that is used in the case of a tie.

By using [].reduce, you can flatten an array of comparison functions into a single one.

const sortByMapped = map => compareFn => (a,b) => compareFn(map(a),map(b)); const flipComparison = fn => (a,b) => -fn(a,b); const byValue = (a,b) => a - b;  const byPrice = sortByMapped(e => e.price)(byValue); const byRating = sortByMapped(e => e.rating)(flipComparison(byValue));  const sortByFlattened = fns => (a,b) =>    fns.reduce((acc, fn) => acc || fn(a,b), 0);  const byPriceRating = sortByFlattened([byPrice,byRating]);  const restaurants = [   { name: "Foo's Burger Stand", price: 1, rating: 3 },   { name: "The Tapas Bar", price: 3, rating: 4 },   { name: "Baz Pizza", price: 3, rating: 2 },   { name: "Amazing Deal", price: 1, rating: 5 },   { name: "Overpriced", price: 5, rating: 1 },  ];  console.log(restaurants.sort(byPriceRating));  /* {name: "Amazing Deal", price: 1, rating: 5} {name: "Foo's Burger Stand", price: 1, rating: 3} {name: "The Tapas Bar", price: 3, rating: 4} {name: "Baz Pizza", price: 3, rating: 2} {name: "Overpriced", price: 5, rating: 1} */

Writing a random sort

You might want to sort an array “randomly.” One technique that I have seen is to use the following function as the comparison function.

const byRandom = () => Math.random() - .5;

Since Math.random() returns a “random” number between 0 and 1, the byRandom function should return a positive number half of the time and a negative number the other half. This seems like it would be a good solution, but unfortunately, since the comparison function is not “consistent” — meaning it may not return the same value when called multiple times with the same values — it may result in some unexpected results.

For example, let’s take an array of numbers between 0 and 4. If this byRandom function was truly random, it would be expected that the new index of each number would be spread out equally over enough iterations. The original 0 value would be just as likely to be in index 4 as index 0 in the new array. However, in practice, this function will bias each number to its original position.

See the Pen
Array.sort() Random 👎
by Adam Giese (@AdamGiese)
on CodePen.

The “diagonal” from the top-left will statistically have the greatest value. In an ideal and truly random sort, each table cell would hover around 20%.

The fix for this is to find a way to ensure that the comparison function remains consistent. One way to do this is to map the random value to each array element before the comparison, then map it away after.

const sortByMapped = map => compareFn => (a,b) => compareFn(map(a),map(b)); const values = [0,1,2,3,4,5,6,7,8,9]; const withRandom = (e) => ({ random: Math.random(), original: e }); const toOriginal = ({original}) => original; const toRandom = ({random}) => random; const byValue = (a,b) => a - b; const byRandom = sortByMapped(toRandom)(byValue);  const shuffleArray = array => array   .map(withRandom)   .sort(byRandom)   .map(toOriginal);

This ensures that each element has a single random value that is only calculated once per element rather than once per comparison. This removes the sorting bias towards the original position.

See the Pen
Array.sort() Random 👍
by Adam Giese (@AdamGiese)
on CodePen.

The post Level up your .sort game appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Level up your JavaScript error monitoring

(This is a sponsored post.)

Automatically detect and diagnose JavaScript errors impacting your users with Bugsnag. Get comprehensive diagnostic reports, know immediately which errors are worth fixing, and debug in a fraction of the time.

Bugsnag detects every single error and prioritizes errors with the greatest impact on your users. Get support for 50+ platforms and integrate with the development and productivity tools your team already uses.

Bugsnag is used by the world’s top engineering teams including Airbnb, Slack, Pinterest, Lyft, Square, Yelp, Shopify, Docker, and Cisco. Start your free trial today.

Direct Link to ArticlePermalink

The post Level up your JavaScript error monitoring appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Level up your JavaScript error monitoring

(This is a sponsored post.)

Automatically detect and diagnose JavaScript errors impacting your users with Bugsnag. Get comprehensive diagnostic reports, know immediately which errors are worth fixing, and debug in a fraction of the time.

Bugsnag detects every single error and prioritizes errors with the greatest impact on your users. Get support for 50+ platforms and integrate with the development and productivity tools your team already uses.

Bugsnag is used by the world’s top engineering teams including Airbnb, Slack, Pinterest, Lyft, Square, Yelp, Shopify, Docker, and Cisco. Start your free trial today.

Direct Link to ArticlePermalink

The post Level up your JavaScript error monitoring appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Level up your JavaScript error monitoring

(This is a sponsored post.)

Automatically detect and diagnose JavaScript errors impacting your users with Bugsnag. Get comprehensive diagnostic reports, know immediately which errors are worth fixing, and debug in a fraction of the time.

Bugsnag detects every single error and prioritizes errors with the greatest impact on your users. Get support for 50+ platforms and integrate with the development and productivity tools your team already uses.

Bugsnag is used by the world’s top engineering teams including Airbnb, Slack, Pinterest, Lyft, Square, Yelp, Shopify, Docker, and Cisco. Start your free trial today.

Direct Link to ArticlePermalink

The post Level up your JavaScript error monitoring appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]