Jump to Navigation

Around The Web

SVG Circle Decomposition To Paths

Smashing Magazine - Fri, 03/22/2019 - 8:00am
SVG Circle Decomposition To Paths SVG Circle Decomposition To Paths Bryan Rasmussen 2019-03-22T13:00:08+01:00 2019-03-23T22:35:13+00:00

This article starts with a confession: I like to hand-code SVG. It’s not always the case but often enough it could seem peculiar to people who do not share my predilection. There are a good number of benefits in being able to write SVG by hand, such as optimizing SVGs in ways a tool can’t (turning a path into a simpler path or shape), or by simply understanding how libraries like D3 or Greensock work.

With that said, I’d like to look more closely at circular shapes in SVG and things we can do with them when we move past a basic circle. Why circles? Well, I love circles. They’re my favorite shape.

First off (hopefully you’ve seen a basic circle in SVG before), here’s a pen that shows one:

See the Pen circle by Bryan Rasmussen.

A lot of things can be done with a circle: it can be animated and it can have different colors applied to it. Still, there are two very nice things that you cannot have a circle do in SVG 1.1: You cannot make another graphical element move along the circle’s path (using the animateMotion element) and you cannot have shape a text along a circle’s path (this will only be allowed after SVG 2.0 is released).

Turning Our Circle Into A Path

There is a little online tool that can help you create paths out of circles (you can try it out here), but we’re going to do be creating everything from scratch so we can find out what’s really going on behind the scenes.

To make a circular path, we’re going to actually make two arcs, i.e. semicircles that complete the circle in one path. As you’ve probably noticed in the SVG above, the attributes CX, CY, and R respectively define where the circle is drawn along the X and Y axis, while R defines the radius of the circle. The CX and CY create the center of the circle, so the circle is drawn around that point.

Replicating that circle could look like this:

<path d=" M (CX - R), CY a R,R 0 1,0 (R * 2),0 a R,R 0 1,0 -(R * 2),0 " />

Note that CX is the same as the cx attribute of the circle; the same goes for CY and the cy attribute of the circle, as well as R and the r attribute of the circle. The small a character is used to define a segment of an elliptical arc. You can use an optional Z (or z) to close the path.

The lowercase letter a denotes the beginning of an elliptical arc drawn relatively to the current position — or in our specific case:

<path d=" M 25, 50 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0 " />

You can see the magic happening in this pen:

See the Pen circle from path by Bryan Rasmussen.

Hidden underneath the path is a circle with a red fill. As you play around with values of the path, you’ll see that circle as long as the path totally covers the circle (the path itself is a circle of the same size), and we’ll know that we’re doing things right.

One thing you should also know is that as long as you are drawing relative arcs, you don’t need to repeat the a command for each arc you draw. When your first 7 inputs are done for your arc, the second 7 inputs will be taken for the next arc.

You can try this out with the pen above by removing the second a in the path:

a 25,25 0 1,1 50,0 25,25 0 1,1 -50,0

This may look the same, but I prefer to leave it in until I am ready to finish a drawing, and this also helps me to keep track of where I am.

How This Path Works

First, we move to an absolutely positioned X,Y coordinate in the image. It does not draw anything there — it just moves there. Remember that for a circle element CX, CY denotes the center of the circle; but as it happens in the elliptical arc, the true CX and CY of the arc will be calculated from the other properties of that arc.

In other words, if we want our CX to be at 50 and our radius is 25, then we need to move to 50 - 25 (if we are drawing from left to right, of course). This means that our first arc is drawn from 25 X, 50 Y which results to our first arc being 25,25 0 1,0 50,0.

Let’s break down what the value 25,25 0 1,0 50,0 of our arc actually means:

  • 25: The relative X radius of the arc;
  • 25: The relative Y radius of the arc;
  • 0 1,0: I’m not going to talk about the three middle values (rotation, large-arc-flag, and the sweep-flag properties) because they are not very important in the context of the current example as long as they are the same for both arcs;
  • 50: The ending X coordinate (relative) of the arc;
  • 0: The ending Y coordinate (relative) of the arc.

The second arc is a 25,25 0 1,0 -50,0. Keep in mind that this arc will start drawing from wherever the last arc stopped drawing. Of course, the X and Y radius are the same (25), but the ending X coordinate is -50 of where the current one is.

Obviously this circle could have been drawn in many different ways. This process of turning a circle into a path is known as decomposition. In the SVG 2 spec decomposition of a circle will be done with 4 arcs, however, the method it recommends is not possible to use yet, as it currently depends on a feature named segment-completing close path which has not yet been specified.

In order to show you that we can draw the circle in a lot of ways, I have prepared a little pen with various examples:

See the Pen all circles by Bryan Rasmussen.

If you take a closer look, you’ll see our original circle along with five different examples of how to draw paths on top of that circle. Each path has a child desc element describing the use of CX, CY and R values to build the circle. The first example is the one we discussed here while three others use variations that should be comprehensible from reading the code; the last examples uses four semicircular arcs instead of two, replicating somewhat the process described in the SVG 2 spec linked above.

The circles are layered on top of each other using SVG’s natural z-indexing of placing elements that come later in the markup on top of the ones that come earlier.

If you click on the circular paths in the pen, the first click will print out how the path is structured to the console and add a class to the element so that you will see the stroke color of how the circle is drawn (you can see that the first circle is drawn with a starting wedge from the stroke). The second click will remove the circle so you have the ability to interact with the circle below.

Each circle has a different fill color; the actual circle element is yellow and will say “You clicked on the circle” to the console whenever it is clicked on. You can also, of course, simply read the code as the desc elements are quite straightforward.

Going From A Path To A Circle

I suppose you’ve noticed that while there are many different ways to draw the circle, the paths used still look pretty similar. Often — especially in SVGs output from a drawing program — circles will be represented by paths. This is probably due to optimization of the graphics program code; once you have the code to draw a path you can draw anything, so just use that. This can lead to somewhat bloated SVGs that are hard to reason about.

Recommended reading: “Tips For Creating And Exporting Better SVGs For The Web” by Sara Soueidan

Let’s take the following SVG from Wikipedia as an example. When you look at the code for that file, you will see that it has a lot of editor cruft once you’ve run it through Jake Archibald’s SVGOMG! (which you can read more about here). You’ll end up with something like the following file which has been pretty optimized, but the circles in the document are still rendered as paths:

See the Pen Wikipedia Screw Head Clutch Type A by Bryan Rasmussen.

So, let’s see if we can figure out what those circles should be if they were actual circle elements given what we know about how paths work. The first path in the document is obviously not a circle while the next two are (showing just the d attribute):

M39 20a19 19 0 1 1-38 0 19 19 0 1 1 38 0z M25 20a5 5 0 1 1-10 0 5 5 0 1 1 10 0z

So remembering that the second a can be left out, let’s rewrite these to make a little more sense. (The first path is the big circle.)

M39 20 a19 19 0 1 1-38 0 a19 19 0 1 1 38 0z

Those arcs are then obviously the following:

aR R 0 1 1 - (R * 2) 0 aR R 0 1 1 (R * 2) 0

This means that our circle radius is 19, but what are our CX and CY values? I think our M39 is actually CX + R, which means that CX is 20 and CY is 20 too.

Let’s say you add in a circle after all the paths like this:

<circle fill="none" stroke-width="1.99975" stroke="red" r="19" cx="20" cy="20" />

You will see that is correct, and that the red stroked circle covers exactly the large circle. The second circle path reformulated looks like this:

M25 20 a5 5 0 1 1-10 0 5 5 0 1 1 10 0z

Obviously, the radius is 5, and I bet our CX and CY values are the same as before: - 20.

Note: If CX = 20, then CX + R = 25. The circle is sitting inside the bigger one at the center, so obviously it should have the same CX and CY values.

Add the following circle at the end of the paths:

<circle fill="yellow" r="5" cx="20" cy="20" />

You can now see that this is correct by taking a look at the following pen:

See the Pen Wikipedia Screw Head Clutch Type A_ with example circles by Bryan Rasmussen.

Now that we know what the circles should be, we can remove those unneeded paths and actually create the circles — as you can see here:

See the Pen Wikipedia Screw Head Clutch Type A optimized by Bryan Rasmussen.

Using Our Circular Path For Wrapping Text

So now that we have our circles in paths, we can wrap text on those paths. Below is a pen with the same paths as our previous “All Circles” pen, but with text wrapped on the path. Whenever you click on a path, that path will be deleted and the text will be wrapped on the next available path, like so:

See the Pen all circles wrapped Text by Bryan Rasmussen.

Looking at the different paths, you’ll see tiny differences between each one (more on that in a bit), but first there is a little cross-browser incompatibility to be seen — especially noticeable in the first path:

Firefox Developer Chrome Microsoft Edge

The reason why the starting “S” of “Smashing” is sitting at that funny angle in the Firefox solution is that it is where we actually started drawing our path at (due to the v-R command we used). This is more obvious in the Chrome version where you can clearly see the first pie-shaped wedge of our circle that we drew:

Chrome does not follow all the wedges, so this is the result when you change the text to be “Smashing Magazine”.

The reason is that Chrome has a bug regarding inheritance of the textLength attribute declared on the parent text element. If you want them both to look the same, put the textLength attribute on the textPath element as well as the text. Why? Because it turns out that Firefox Developer has the same bug if the textLength attribute is not specified on the text element (this has been the case for some years now).

Microsoft Edge has a totally different bug; it can’t handle whitespace in between the Text and the child TextPath element. Once you have removed whitespace, and put the textLength attribute on both the text and textPath elements, they will all look relatively the same (with small variations due to differences in default fonts and so forth). So, three different bugs on three different browsers — this is why people often prefer to work with libraries!

The following pen shows how the problems can be fixed:

See the Pen all circles wrapped Text fixed TextLength by Bryan Rasmussen.

I’ve also removed the various fill colors because it makes it easier to see the text wrapping. Removing the fill colors means that my little function to allow you to cycle through the paths and see how they look won’t work unless I add a pointer-events="all" attribute, so I’ve added those as well.

Note: You can read more about the reasons for that in “Managing SVG Interaction With The Pointer Events Property” explained by Tiffany B. Brown.

We’ve already discussed the wrapping of the multiarc path, so let’s now look at the others. Since we have one path we are wrapping on, the text will always move in the same direction.

Image Path Explanation M CX, CY
a R, R 0 1,0 -(R * 2), 0
a R, R 0 1,0 R * 2, 0
and uses the translate function to move +R on the X axis. The starting position for our textPath (since we have not specified it in any way) is determined by our first ending arc -(R * 2), given the radius that the arc itself has. M (CX + R), CY
a R,R 0 1,0 -(R * 2),0
a R,R 0 1,0 (R * 2),0 Same applies as the previous path. M CX CY
m -R, 0
a R,R 0 1,0 (R * 2),0
a R,R 0 1,0 -(R * 2),0 Since we are ending at (R * 2 ) in our first arc, we will obviously be starting at the opposite position. In other words, this one starts where our previous two paths ended. M (CX - R), CY
a R,R 0 1,1 (R * 2),0
a R,R 0 1,1 -(R * 2),0 This starts in the same position as the last one due to (R * 2), but it is running clockwise because we have set the sweep-flag property (marked in yellow) to 1.

We‘ve seen how to wrap text on a single path in a circle. Let’s now take a look at how we can break up that path into two paths and the benefits you can get from that.

Breaking Our Paths Into Parts

There are a lot of things you can do with the text in your path, i.e. achieving stylistic effects with tspan elements, setting the offset of the text, or animating the text. Basically, whatever you do will be constrained by the path itself. But by breaking up our multiarc paths into single arc paths, we can play around with the direction of our text, the z-indexing of different parts of our text, and achieving more complex animations.

First, we are going to want to use another SVG image to show some of the effects. I will be using the diamond from the article on pointer events which I mentioned earlier. First, let’s show what it will look like with a single path circular text laid on top of it.

Let’s assume that our circle is CX 295, CY 200, R 175. Now, following the Circular path method, we now see the following:

M (CX - R), CY a R,R 0 1,1 (R * 2),0 a R,R 0 1,1 -(R * 2),0

See the Pen SVG Amethyst by Bryan Rasmussen.

I’m not going to talk about the path or the text size, fill or stroke color. We should all understand that by now, and be able to make it be whatever we want it to be. But by looking at the text, we can see some downsides or limitations right away:

  • The text all runs in one direction;
  • It might be nice to have some of the text go behind the amethyst, especially where it says MAGAZINE. In order to make the ‘M’ and ‘E’ line up on the circle, the ‘A’ has to be on the side lower point of the amethyst, which feels sort of unbalanced in another way. (I feel like the ‘A’ should be precisely positioned and pointing down at that point.)

If we want to fix these issues, we need to split our single path into two. In the following pen, I have separated the path into two paths, (and placed them into the defs area of the SVG for our textPaths to reference):

See the Pen SVG Amethyst two paths by Bryan Rasmussen.

Again, assuming our CX is 295, CY 200, R 175, then the two paths are in the format of the following (for the top semicircular path):

M (CX - R), CY a R,R 0 1,1 (R * 2),0

And the following for the bottom:

M (CX + R), CY a R,R 0 1,1 -(R * 2),0

However, we still have circular text that moves all in the same direction. To fix that for everything but Edge, all you have to do is to add the side="right" attribute to the text element that holds the ‘MAGAZINE’ textPath.

Making The Text Go Another Direction

If we want to support as many browsers as we can, we have to alter the path and not rely on the side attribute which is not fully supported. What we can do is to copy our top semicircle path, but change the sweep from 1 to 0:

Before:

M 120, 200 a 175,175 0 1,1 350,0

After:

M 120, 200 a 175,175 0 1,0 350,0

But our text is now drawn on the inner circle defined by the sweep and it won’t look so nice in different browsers. This means that we’re going to have to move the position of our path to align with the ‘S’ of ‘Smashing’, make the ending X of the path greater, and set some offset to the text. As you can see, there is also a little text difference between Firefox and the others which we can improve by increasing the textLength attribute on the text element, as well as removing whitespace from the textPath (since Firefox evidently thinks whitespace is meaningful).

The solution:

See the Pen SVG Amethyst two paths fixed by Bryan Rasmussen.

Change The Z-Index Of Part Of Our Circular Text

Finally, we want to make our text goes both in front and behind the amethyst. Well, that’s easy. Remember that SVG’s z-indexing of element is based by where they are in the markup? So if we have two elements, element 1 will be drawn behind element 2. Next, all we have to do is to move a text element up in our SVG markup so it is drawn before the amethyst.

You can see the result below in which parts of the word ‘MAGAZINE’ are hidden by the lower point of the amethyst.

See the Pen SVG Amethyst two paths z-index by Bryan Rasmussen.

If you take a look at the markup, you can see that the lower semicircle of text has been moved to be before the path that draws the amethyst.

Animating The Parts Of Our Circle

So now we have the ability to make circular text by completely controlling the directionality of the parts of our text by putting the text into two semicircles. This can, of course, also be exploited to make animations of the text. Making cross-browser SVG animations is really the subject of another article (or a lot more articles). These examples will only work in Chrome and Firefox because of using the SMIL-animations syntax instead of CSS keyframes or tools like Greensock. But it gives a good indicator of the effects you can achieve by animating the decomposed circle.

Take the following pen:

See the Pen SVG Amethyst two paths animated by Bryan Rasmussen.

Please press the ‘Rerun’ button on the codepen to see the animation in action. The two parts of our circular text begin animating at the same time, but have a different duration so they end at different times. Because we are animating the textLength attribute, we have put two animate directives under each text — one for the text element (so Firefox will work) and one for the textpath element (so Chrome will work).

Conclusion

In this article, we’ve seen how to turn a circle into a path and back again, in order to better understand when we need to optimize a path and when not. We’ve seen how turning the circle into a path frees us up to placing the text on the circular path, but also how to further split the circular path into semicircles and gain fuller control over directionality and animation of the component parts of our circular text.

Further Reading on SmashingMag: (dm, ra, yk, il)
Categories: Around The Web

How To Make A Speech Synthesis Editor

Smashing Magazine - Thu, 03/21/2019 - 8:00am
How To Make A Speech Synthesis Editor How To Make A Speech Synthesis Editor Knut Melvær 2019-03-21T13:00:16+01:00 2019-03-23T22:35:13+00:00

When Steve Jobs unveiled the Macintosh in 1984, it said “Hello” to us from the stage. Even at that point, speech synthesis wasn’t really a new technology: Bell Labs developed the vocoder as early as in the late 30s, and the concept of a voice assistant computer made it into people’s awareness when Stanley Kubrick made the vocoder the voice of HAL9000 in 2001: A Space Odyssey (1968).

It wasn’t before the introduction of Apple’s Siri, Amazon Echo, and Google Assistant in the mid 2015s that voice interfaces actually found their way into a broader public’s homes, wrists, and pockets. We’re still in an adoption phase, but it seems that these voice assistants are here to stay.

In other words, the web isn’t just passive text on a screen anymore. Web editors and UX designers have to get accustomed to making content and services that should be spoken out loud.

We’re already moving fast towards using content management systems that let us work with our content headlessly and through APIs. The final piece is to make editorial interfaces that make it easier to tailor content for voice. So let’s do just that!

What Is SSML

While web browsers use W3C’s specification for HyperText Markup Language (HTML) to visually render documents, most voice assistants use Speech Synthesis Markup Language (SSML) when generating speech.

A minimal example using the root element <speak>, and the paragraph (<p>) and sentence (<s>) tags:

<speak> <p> <s>This is the first sentence of the paragraph.</s> <s>Here’s another sentence.</s> </p> </speak> Press play to listen to the snippet: Your browser does not support the audio element.

Where SSML gets existing is when we introduce tags for <emphasis> and <prosody> (pitch):

<speak> <p> <s>Put some <emphasis strength="strong">extra weight on these words</emphasis></s> <s>And say <prosody pitch="high" rate="fast">this a bit higher and faster</prosody>!</s> </p> </speak> Press play to listen to the snippet: Your browser does not support the audio element.

SSML has more features, but this is enough to get a feel for the basics. Now, let’s take a closer look at the editor that we will use to make the speech synthesis editing interface.

The Editor For Portable Text

To make this editor, we’ll use the editor for Portable Text that features in Sanity.io. Portable Text is a JSON specification for rich text editing, that can be serialized into any markup language, such as SSML. This means you can easily use the same text snippet in multiple places using different markup languages.

Sanity.io’s default editor for Portable Text (Large preview) Installing Sanity

Sanity.io is a platform for structured content that comes with an open-source editing environment built with React.js. It takes two minutes to get it all up and running.

Type npm i -g @sanity/cli && sanity init into your terminal, and follow the instructions. Choose “empty”, when you’re prompted for a project template.

If you don’t want to follow this tutorial and make this editor from scratch, you can also clone this tutorial’s code and follow the instructions in README.md.

When the editor is downloaded, you run sanity start in the project folder to start it up. It will start a development server that use Hot Module Reloading to update changes as you edit its files.

How To Configure Schemas In Sanity Studio Creating The Editor Files

We’ll start by making a folder called ssml-editor in the /schemas folder. In that folder, we’ll put some empty files:

/ssml-tutorial/schemas/ssml-editor ├── alias.js ├── emphasis.js ├── annotations.js ├── preview.js ├── prosody.js ├── sayAs.js ├── blocksToSSML.js ├── speech.js ├── SSMLeditor.css └── SSMLeditor.js

Now we can add content schemas in these files. Content schemas are what defines the data structure for the rich text, and what Sanity Studio uses to generate the editorial interface. They are simple JavaScript objects that mostly require just a name and a type.

We can also add a title and a description to make a bit nicer for editors. For example, this is a schema for a simple text field for a title:

export default { name: 'title', type: 'string', title: 'Title', description: 'Titles should be short and descriptive' } The studio with our title field and the default editor (Large preview)

Portable Text is built on the idea of rich text as data. This is powerful because it lets you query your rich text, and convert it into pretty much any markup you want.

It is an array of objects called “blocks” which you can think of as the “paragraphs”. In a block, there is an array of children spans. Each block can have a style and a set of mark definitions, which describe data structures distributed on the children spans.

Sanity.io comes with an editor that can read and write to Portable Text, and is activated by placing the block type inside an array field, like this:

// speech.js export default { name: 'speech', type: 'array', title: 'SSML Editor', of: [ { type: 'block' } ] }

An array can be of multiple types. For an SSML-editor, those could be blocks for audio files, but that falls outside of the scope of this tutorial.

The last thing we want to do is to add a content type where this editor can be used. Most assistants use a simple content model of “intents” and “fulfillments”:

  • Intents Usually a list of strings used by the AI model to delineate what the user wants to get done.
  • Fulfillments This happens when an “intent” is identified. A fulfillment often is — or at least — comes with some sort of response.

So let’s make a simple content type called fulfillment that use the speech synthesis editor. Make a new file called fulfillment.js and save it in the /schema folder:

// fulfillment.js export default { name: 'fulfillment', type: 'document', title: 'Fulfillment', of: [ { name: 'title', type: 'string', title: 'Title', description: 'Titles should be short and descriptive' }, { name: 'response', type: 'speech' } ] }

Save the file, and open schema.js. Add it to your studio like this:

// schema.js import createSchema from 'part:@sanity/base/schema-creator' import schemaTypes from 'all:part:@sanity/base/schema-type' import fullfillment from './fullfillment' import speech from './speech' export default createSchema({ name: 'default', types: schemaTypes.concat([ fullfillment, speech, ]) })

If you now run sanity start in your command line interface within the project’s root folder, the studio will start up locally, and you’ll be able to add entries for fulfillments. You can keep the studio running while we go on, as it will auto-reload with new changes when you save the files.

Adding SSML To The Editor

By default, the block type will give you a standard editor for visually oriented rich text with heading styles, decorator styles for emphasis and strong, annotations for links, and lists. Now we want to override those with the audial concepts found in SSML.

We begin with defining the different content structures, with helpful descriptions for the editors, that we will add to the block in SSMLeditorSchema.js as configurations for annotations. Those are “emphasis”, “alias”, “prosody”, and “say as”.

Emphasis

We begin with “emphasis”, which controls how much weight is put on the marked text. We define it as a string with a list of predefined values that the user can choose from:

// emphasis.js export default { name: 'emphasis', type: 'object', title: 'Emphasis', description: 'The strength of the emphasis put on the contained text', fields: [ { name: 'level', type: 'string', options: { list: [ { value: 'strong', title: 'Strong' }, { value: 'moderate', title: 'Moderate' }, { value: 'none', title: 'None' }, { value: 'reduced', title: 'Reduced' } ] } } ] } Alias

Sometimes the written and the spoken term differ. For instance, you want to use the abbreviation of a phrase in a written text, but have the whole phrase read aloud. For example:

<s>This is a <sub alias="Speech Synthesis Markup Language">SSML</sub> tutorial</s> Press play to listen to the snippet: Your browser does not support the audio element.

The input field for the alias is a simple string:

// alias.js export default { name: 'alias', type: 'object', title: 'Alias (sub)', description: 'Replaces the contained text for pronunciation. This allows a document to contain both a spoken and written form.', fields: [ { name: 'text', type: 'string', title: 'Replacement text', } ] } Prosody

With the prosody property we can control different aspects how text should be spoken, like pitch, rate, and volume. The markup for this can look like this:

<s>Say this with an <prosody pitch="x-low">extra low pitch</prosody>, and this <prosody rate="fast" volume="loud">loudly with a fast rate</prosody></s> Press play to listen to the snippet: Your browser does not support the audio element.

This input will have three fields with predefined string options:

// prosody.js export default { name: 'prosody', type: 'object', title: 'Prosody', description: 'Control of the pitch, speaking rate, and volume', fields: [ { name: 'pitch', type: 'string', title: 'Pitch', description: 'The baseline pitch for the contained text', options: { list: [ { value: 'x-low', title: 'Extra low' }, { value: 'low', title: 'Low' }, { value: 'medium', title: 'Medium' }, { value: 'high', title: 'High' }, { value: 'x-high', title: 'Extra high' }, { value: 'default', title: 'Default' } ] } }, { name: 'rate', type: 'string', title: 'Rate', description: 'A change in the speaking rate for the contained text', options: { list: [ { value: 'x-slow', title: 'Extra slow' }, { value: 'slow', title: 'Slow' }, { value: 'medium', title: 'Medium' }, { value: 'fast', title: 'Fast' }, { value: 'x-fast', title: 'Extra fast' }, { value: 'default', title: 'Default' } ] } }, { name: 'volume', type: 'string', title: 'Volume', description: 'The volume for the contained text.', options: { list: [ { value: 'silent', title: 'Silent' }, { value: 'x-soft', title: 'Extra soft' }, { value: 'medium', title: 'Medium' }, { value: 'loud', title: 'Loud' }, { value: 'x-loud', title: 'Extra loud' }, { value: 'default', title: 'Default' } ] } } ] } Say As

The last one we want to include is <say-as>. This tag lets us exercise a bit more control over how certain information is pronounced. We can even use it to bleep out words if you need to redact something in voice interfaces. That’s @!%&© useful!

<s>Do I have to <say-as interpret-as="expletive">frakking</say-as> <say-as interpret-as="verbatim">spell</say-as> it out for you!?</s> Press play to listen to the snippet: Your browser does not support the audio element. // sayAs.js export default { name: 'sayAs', type: 'object', title: 'Say as...', description: 'Lets you indicate information about the type of text construct that is contained within the element. It also helps specify the level of detail for rendering the contained text.', fields: [ { name: 'interpretAs', type: 'string', title: 'Interpret as...', options: { list: [ { value: 'cardinal', title: 'Cardinal numbers' }, { value: 'ordinal', title: 'Ordinal numbers (1st, 2nd, 3th...)' }, { value: 'characters', title: 'Spell out characters' }, { value: 'fraction', title: 'Say numbers as fractions' }, { value: 'expletive', title: 'Blip out this word' }, { value: 'unit', title: 'Adapt unit to singular or plural' }, { value: 'verbatim', title: 'Spell out letter by letter (verbatim)' }, { value: 'date', title: 'Say as a date' }, { value: 'telephone', title: 'Say as a telephone number' } ] } }, { name: 'date', type: 'object', title: 'Date', fields: [ { name: 'format', type: 'string', description: 'The format attribute is a sequence of date field character codes. Supported field character codes in format are {y, m, d} for year, month, and day (of the month) respectively. If the field code appears once for year, month, or day then the number of digits expected are 4, 2, and 2 respectively. If the field code is repeated then the number of expected digits is the number of times the code is repeated. Fields in the date text may be separated by punctuation and/or spaces.' }, { name: 'detail', type: 'number', validation: Rule => Rule.required() .min(0) .max(2), description: 'The detail attribute controls the spoken form of the date. For detail='1' only the day fields and one of month or year fields are required, although both may be supplied' } ] } ] }

Now we can import these in an annotations.js file, which makes things a bit tidier.

// annotations.js export {default as alias} from './alias' export {default as emphasis} from './emphasis' export {default as prosody} from './prosody' export {default as sayAs} from './sayAs'

Now we can import these annotation types into our main schemas:

// schema.js import createSchema from "part:@sanity/base/schema-creator" import schemaTypes from "all:part:@sanity/base/schema-type" import fulfillment from './fulfillment' import speech from './ssml-editor/speech' import { alias, emphasis, prosody, sayAs } from './annotations' export default createSchema({ name: "default", types: schemaTypes.concat([ fulfillment, speech, alias, emphasis, prosody, sayAs ]) })

Finally, we can now add these to the editor like this:

// speech.js export default { name: 'speech', type: 'array', title: 'SSML Editor', of: [ { type: 'block', styles: [], lists: [], marks: { decorators: [], annotations: [ {type: 'alias'}, {type: 'emphasis'}, {type: 'prosody'}, {type: 'sayAs'} ] } } ] }

Notice that we also added empty arrays to styles, and decorators. This disables the default styles and decorators (like bold and emphasis) since they don’t make that much sense in this specific case.

Customizing The Look And Feel

Now we have the functionality in place, but since we haven’t specified any icons, each annotation will use the default icon, which makes the editor hard to actually use for authors. So let’s fix that!

With the editor for Portable Text it’s possible to inject React components both for the icons and for how the marked text should be rendered. Here, we’ll just let some emoji do the work for us, but you could obviously go far with this, making them dynamic and so on. For prosody we’ll even make the icon change depending on the volume selected. Note that I omitted the fields in these snippets for brevity, you shouldn’t remove them in your local files.

// alias.js import React from 'react' export default { name: 'alias', type: 'object', title: 'Alias (sub)', description: 'Replaces the contained text for pronunciation. This allows a document to contain both a spoken and written form.', fields: [ /* all the fields */ ], blockEditor: { icon: () => '
Categories: Around The Web

How To Build An Endless Runner Game In Virtual Reality (Part 3)

Smashing Magazine - Wed, 03/20/2019 - 8:00am
How To Build An Endless Runner Game In Virtual Reality (Part 3) How To Build An Endless Runner Game In Virtual Reality (Part 3) Alvin Wan 2019-03-20T13:00:35+01:00 2019-03-23T22:35:13+00:00

And so our journey continues. In this final part of my series on how to build an endless runner VR game, I’ll show you how you can synchronize the game state between two devices which will move you one step closer to building a multiplayer game. I’ll specifically introduce MirrorVR which is responsible for handling the mediating server in client-to-client communication.

Note: This game can be played with or without a VR headset. You can view a demo of the final product at ergo-3.glitch.me.

To get started, you will need the following.

  • Internet access (specifically to glitch.com);
  • A Glitch project completed from part 2 of this tutorial. You can start from the part 2 finished product by navigating to https://glitch.com/edit/#!/ergo-2 and clicking “Remix to edit”;
  • A virtual reality headset (optional, recommended). (I use Google Cardboard, which is offered at $15 a piece.)
Step 1: Display Score

The game as-is functions at a bare minimum, where the player is given a challenge: avoid the obstacles. However, outside of object collisions, the game does not provide feedback to the player regarding progress in the game. To remedy this, you will implement the score display in this step. The score will be large text object placed in our virtual reality world, as opposed to an interface glued to the user’s field of view.

In virtual reality generally, the user interface is best integrated into the world rather than stuck to the user’s head.

Score display (Large preview)

Start by adding the object to index.html. Add a text mixin, which will be reused for other text elements:

<a-assets> ... <a-mixin id="text" text=" font:exo2bold; anchor:center; align:center;"></a-mixin> ... </a-assets>

Next, add a text element to the platform, right before the player:

<!-- Score --> <a-text id="score" value="" mixin="text" height="40" width="40" position="0 1.2 -3" opacity="0.75"></a-text> <!-- Player --> ...

This adds a text entity to the virtual reality scene. The text is not currently visible, because its value is set to empty. However, you will now populate the text entity dynamically, using JavaScript. Navigate to assets/ergo.js. After the collisions section, add a score section, and define a number of global variables:

  • score: the current game score.
  • countedTrees: IDs of all trees that are included in the score. (This is because collision tests may trigger multiple times for the same tree.)
  • scoreDisplay: reference to the DOM object, corresponding to a text object in the virtual reality world.
/********* * SCORE * *********/ var score; var countedTrees; var scoreDisplay;

Next, define a setup function to initialize our global variables. In the same vein, define a teardown function.

... var scoreDisplay; function setupScore() { score = 0; countedTrees = new Set(); scoreDisplay = document.getElementById('score'); } function teardownScore() { scoreDisplay.setAttribute('value', ''); }

In the Game section, update gameOver, startGame, and window.onload to include score setup and teardown.

/******** * GAME * ********/ function gameOver() { ... teardownScore(); } function startGame() { ... setupScore(); addTreesRandomlyLoop(); } window.onload = function() { setupScore(); ... }

Define a function that increments the score for a particular tree. This function will check against countedTrees to ensure that the tree is not double counted.

function addScoreForTree(tree_id) { if (countedTrees.has(tree_id)) return; score += 1; countedTrees.add(tree_id); }

Additionally, add a utility to update the score display using the global variable.

function updateScoreDisplay() { scoreDisplay.setAttribute('value', score); }

Update the collision testing accordingly in order to invoke this score-incrementing function whenever an obstacle has passed the player. Still in assets/ergo.js, navigate to the collisions section. Add the following check and update.

AFRAME.registerComponent('player', { tick: function() { document.querySelectorAll('.tree').forEach(function(tree) { ... if (position.z > POSITION_Z_LINE_END) { addScoreForTree(tree_id); updateScoreDisplay(); } }) } })

Finally, update the score display as soon as the game starts. Navigate to the Game section, and add updateScoreDisplay(); to startGame:

function startGame() { ... setupScore(); updateScoreDisplay(); ... }

Ensure that assets/ergo.js and index.html match the corresponding source code files. Then, navigate to your preview. You should see the following:

Score display (Large preview)

This concludes the score display. Next, we will add proper start and Game Over menus, so that the player can replay the game as desired.

Step 2: Add Start Menu

Now that the user can keep track of the progress, you will add finishing touches to complete the game experience. In this step, you will add a Start menu and a Game Over menu, letting the user start and restart games.

Let’s begin with the Start menu where the player clicks a “Start” button to begin the game. For the second half of this step, you will add a Game Over menu, with a “Restart” button:

Start and game over menus (Large preview)

Navigate to index.html in your editor. Then, find the Mixins section. Here, append the title mixin, which defines styles for particularly large text. We use the same font as before, align text to the center, and define a size appropriate for the type of text. (Note below that anchor is where a text object is anchored to its position.)

<a-assets> ... <a-mixin id="title" text=" font:exo2bold; height:40; width:40; opacity:0.75; anchor:center; align:center;"></a-mixin> </a-assets>

Next, add a second mixin for secondary headings. This text is slightly smaller but is otherwise identical to the title.

<a-assets> ... <a-mixin id="heading" text=" font:exo2bold; height:10; width:10; opacity:0.75; anchor:center; align:center;"></a-mixin> </a-assets>

For the third and final mixin, define properties for descriptive text — even smaller than secondary headings.

<a-assets> ... <a-mixin id="copy" text=" font:exo2bold; height:5; width:5; opacity:0.75; anchor:center; align:center;"></a-mixin> </a-assets>

With all text styles defined, you will now define the in-world text objects. Add a new Menus section beneath the Score section, with an empty container for the Start menu:

<!-- Score --> ... <!-- Menus --> <a-entity id="menu-container"> <a-entity id="start-menu" position="0 1.1 -3"> </a-entity> </a-entity>

Inside the start menu container, define the title and a container for all non-title text:

... <a-entity id="start-menu" ...> <a-entity id="start-copy" position="0 1 0"> </a-entity> <a-text value="ERGO" mixin="title"></a-text> </a-entity> </a-entity>

Inside the container for non-title text, add instructions for playing the game:

<a-entity id="start-copy"...> <a-text value="Turn left and right to move your player, and avoid the trees!" mixin="copy"></a-text> </a-entity>

To complete the Start menu, add a button that reads “Start”:

<a-entity id="start-copy"...> ... <a-text value="Start" position="0 0.75 0" mixin="heading"></a-text> <a-box id="start-button" position="0 0.65 -0.05" width="1.5" height="0.6" depth="0.1"></a-box> </a-entity>

Double-check that your Start menu HTML code matches the following:

<!-- Menus --> <a-entity id="menu-container"> <a-entity id="start-menu" position="0 1.1 -3"> <a-entity id="start-copy" position="0 1 0"> <a-text value="Turn left and right to move your player, and avoid the trees!" mixin="copy"></a-text> <a-text value="Start" position="0 0.75 0" mixin="heading"></a-text> <a-box id="start-button" position="0 0.65 -0.05" width="1.5" height="0.6" depth="0.1"></a-box> </a-entity> <a-text value="ERGO" mixin="title"></a-text> </a-entity> </a-entity>

Navigate to your preview, and you will see the following Start menu:

Start menu (Large preview)

Still in the Menus section (directly beneath the start menu), add the game-over menu using the same mixins:

<!-- Menus --> <a-entity id="menu-container"> ... <a-entity id="game-over" position="0 1.1 -3"> <a-text value="?" mixin="heading" id="game-score" position="0 1.7 0"></a-text> <a-text value="Score" mixin="copy" position="0 1.2 0"></a-text> <a-entity id="game-over-copy"> <a-text value="Restart" mixin="heading" position="0 0.7 0"></a-text> <a-box id="restart-button" position="0 0.6 -0.05" width="2" height="0.6" depth="0.1"></a-box> </a-entity> <a-text value="Game Over" mixin="title"></a-text> </a-entity> </a-entity>

Navigate to your JavaScript file, assets/ergo.js. Create a new Menus section before the Game section. Additionally, define three empty functions: setupAllMenus, hideAllMenus, and showGameOverMenu.

/******** * MENU * ********/ function setupAllMenus() { } function hideAllMenus() { } function showGameOverMenu() { } /******** * GAME * ********/

Next, update the Game section in three places. In gameOver, show the Game Over menu:

function gameOver() { ... showGameOverMenu(); } ``` In `startGame`, hide all menus: ``` function startGame() { ... hideAllMenus(); }

Next, in window.onload, remove the direct invocation to startGame and instead call setupAllMenus. Update your listener to match the following:

window.onload = function() { setupAllMenus(); setupScore(); setupTrees(); }

Navigate back to the Menu section. Save references to various DOM objects:

/******** * MENU * ********/ var menuStart; var menuGameOver; var menuContainer; var isGameRunning = false; var startButton; var restartButton; function setupAllMenus() { menuStart = document.getElementById('start-menu'); menuGameOver = document.getElementById('game-over'); menuContainer = document.getElementById('menu-container'); startButton = document.getElementById('start-button'); restartButton = document.getElementById('restart-button'); }

Next, bind both the “Start” and “Restart” buttons to startGame:

function setupAllMenus() { ... startButton.addEventListener('click', startGame); restartButton.addEventListener('click', startGame); }

Define showStartMenu and invoke it from setupAllMenus:

function setupAllMenus() { ... showStartMenu(); } function hideAllMenus() { } function showGameOverMenu() { } function showStartMenu() { }

To populate the three empty functions, you will need a few helper functions. Define the following two functions, which accepts a DOM element representing an A-Frame VR entity and shows or hides it. Define both functions above showAllMenus:

... var restartButton; function hideEntity(el) { el.setAttribute('visible', false); } function showEntity(el) { el.setAttribute('visible', true); } function showAllMenus() { ...

First populate hideAllMenus. You will remove the objects from sight, then remove click listeners for both menus:

function hideAllMenus() { hideEntity(menuContainer); startButton.classList.remove('clickable'); restartButton.classList.remove('clickable'); }

Second, populate showGameOverMenu. Here, restore the container for both menus, as well as the Game Over menu and the ‘Restart’ button’s click listener. However, remove the ‘Start’ button’s click listener, and hide the ‘Start’ menu.

function showGameOverMenu() { showEntity(menuContainer); hideEntity(menuStart); showEntity(menuGameOver); startButton.classList.remove('clickable'); restartButton.classList.add('clickable'); }

Third, populate showStartMenu. Here, reverse all changes that the showGameOverMenu effected.

function showStartMenu() { showEntity(menuContainer); hideEntity(menuGameOver); showEntity(menuStart); startButton.classList.add('clickable'); restartButton.classList.remove('clickable'); }

Double-check that your code matches the corresponding source files. Then, navigate to your preview, and you will observe the following behavior:

Start and Game Over menus (Large preview)

This concludes the Start and Game Over menus.

Congratulations! You now have a fully functioning game with a proper start and proper end. However, we have one more step left in this tutorial: We need to synchronize the game state between different player devices. This will move us one step closer towards multiplayer games.

Step 3: Synchronizing Game State With MirrorVR

In a previous tutorial, you learned how to send real-time information across sockets, to facilitate one-way communication between a server and a client. In this step, you will build on top of a fully-fledged product of that tutorial, MirrorVR, which handles the mediating server in client-to-client communication.

Note: You can learn more about MirrorVR here.

Navigate to index.html. Here, we will load MirrorVR and add a component to the camera, indicating that it should mirror a mobile device’s view where applicable. Import the socket.io dependency and MirrorVR 0.2.3.

<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.js"></script> <script src="https://cdn.jsdelivr.net/gh/alvinwan/mirrorvr@0.2.2/dist/mirrorvr.min.js"></script>

Next, add a component, camera-listener, to the camera:

<a-camera camera-listener ...>

Navigate to assets/ergo.js. In this step, the mobile device will send commands, and the desktop device will only mirror the mobile device.

To facilitate this, you need a utility to distinguish between desktop and mobile devices. At the end of your file, add a mobileCheck function after shuffle:

/** * Checks for mobile and tablet platforms. */ function mobileCheck() { var check = false; (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); return check; };

First, we will synchronize the game start. In startGame, of the Game section, add a mirrorVR notification at the end.

function startGame() { ... if (mobileCheck()) { mirrorVR.notify('startGame', {}) } }

The mobile client now sends notifications about a game starting. You will now implement the desktop’s response.

In the window load listener, invoke a setupMirrorVR function:

window.onload = function() { ... setupMirrorVR(); }

Define a new section above the Game section for the MirrorVR setup:

/************ * MirrorVR * ************/ function setupMirrorVR() { mirrorVR.init(); }

Next, add keyword arguments to the initialization function for mirrorVR. Specifically, we will define the handler for game start notifications. We will additionally specify a room ID; this ensures that anyone loading your application is immediately synchronized.

function setupMirrorVR() { mirrorVR.init({ roomId: 'ergo', state: { startGame: { onNotify: function(data) { hideAllMenus(); setupScore(); updateScoreDisplay(); } }, } }); }

Repeat the same synchronization process for Game Over. In gameOver in the Game section, add a check for mobile devices and send a notification accordingly:

function gameOver() { ... if (mobileCheck()) { mirrorVR.notify('gameOver', {}); } }

Navigate to the MirrorVR section and update the keyword arguments with a gameOver listener:

function setupMirrorVR() { mirrorVR.init({ state: { startGame: {... }, gameOver: { onNotify: function(data) { gameOver(); } }, } }) }

Next, repeat the same synchronization process for the addition of trees. Navigate to addTreesRandomly in the Trees section. Keep track of which lanes receive new trees. Then, directly before the return directive, and send a notification accordingly:

function addTreesRandomly(...) { ... var numberOfTreesAdded ... var position_indices = []; trees.forEach(function (tree) { if (...) { ... position_indices.push(tree.position_index); } }); if (mobileCheck()) { mirrorVR.notify('addTrees', position_indices); } return ... }

Navigate to the MirrorVR section, and update the keyword arguments to mirrorVR.init with a new listener for trees:

function setupMirrorVR() { mirrorVR.init({ state: { ... gameOver: {... }, addTrees: { onNotify: function(position_indices) { position_indices.forEach(addTreeTo) } }, } }) }

Finally, we synchronize the game score. In updateScoreDisplay from the Score section, send a notification when applicable:

function updateScoreDisplay() { ... if (mobileCheck()) { mirrorVR.notify('score', score); } }

Update the mirrorVR initialization for the last time, with a listener for score changes:

function setupMirrorVR() { mirrorVR.init({ state: { addTrees: { }, score: { onNotify: function(data) { score = data; updateScoreDisplay(); } } } }); }

Double-check that your code matches the appropriate source code files for this step. Then, navigate to your desktop preview. Additionally, open up the same URL on your mobile device. As soon as your mobile device loads the webpage, your desktop should immediately start mirroring the mobile device’s game.

Here is a demo. Notice that the desktop cursor is not moving, indicating the mobile device is controlling the desktop preview.

Final result of the endless runner game with MirrorVR game state synchronization (Large preview)

This concludes your augmented project with mirrorVR.

This third step introduced a few basic game state synchronization steps; to make this more robust, you could add more sanity checks and more points of synchronization.

Conclusion

In this tutorial, you added finishing touches to your endless runner game and implemented real-time synchronization of a desktop client with a mobile client, effectively mirroring the mobile device’s screen on your desktop. This concludes the series on building an endless runner game in virtual reality. Along with A-Frame VR techniques, you’ve picked up 3D modeling, client-to-client communication, and other widely applicable concepts.

Next steps can include:

  • More Advanced Modeling
    This means more realistic 3D models, potentially created in a third-party software and imported. For example, (MagicaVoxel) makes creating voxel art simple, and (Blender) is a complete 3D modeling solution.
  • More Complexity
    More complex games, such as a real-time strategy game, could leverage a third-party engine for increased efficiency. This may mean sidestepping A-Frame and webVR entirely, instead publishing a compiled (Unity3d) game.

Other avenues include multiplayer support and richer graphics. With the conclusion of this tutorial series, you now have a framework to explore further.

(rb, ra, il)
Categories: Around The Web

I Used The Web For A Day On Internet Explorer 8

Smashing Magazine - Tue, 03/19/2019 - 7:00am
I Used The Web For A Day On Internet Explorer 8 I Used The Web For A Day On Internet Explorer 8 Chris Ashton 2019-03-19T12:00:08+01:00 2019-03-23T22:35:13+00:00

This article is part of a series in which I attempt to use the web under various constraints, representing a given demographic of user. I hope to raise the profile of difficulties faced by real people, which are avoidable if we design and develop in a way that is sympathetic to their needs.

Last time, I navigated the web for a day using a screen reader. This time, I spent the day using Internet Explorer 8, which was released ten years ago today, on March 19th, 2009.

Who In The World Uses IE8?

Before we start; a disclaimer: I am not about to tell you that you need to start supporting IE8.

There’s every reason to not support IE8. Microsoft officially stopped supporting IE8, IE9 and IE10 over three years ago, and the Microsoft executives are even telling you to stop using Internet Explorer 11.

But as much as we developers hope for it to go away, it just. Won’t. Die. IE8 continues to show up in browser stats, especially outside of the bubble of the Western world.

Browser stats have to be taken with a pinch of salt, but current estimates for IE8 usage worldwide are around 0.3% to 0.4% of the desktop market share. The lower end of the estimate comes from w3counter:

From a peak of almost 30% at the end of 2010, W3Counter now believes IE8 accounts for 0.3% of global usage. (Large preview)

The higher estimate comes from StatCounter (the same data feed used by the “Can I use” usage table). It estimates global IE8 desktop browser proportion to be around 0.37%.

Worldwide usage of IE8 is at 0.37% according to StatCounter. (Large preview)

I suspected we might see higher IE8 usage in certain geographical regions, so drilled into the data by continent.

IE8 Usage By Region

Here is the per-continent IE8 desktop proportion (data from February 2018 — January 2019):

1. Oceania 0.09% 2. Europe 0.25% 3. South America 0.30% 4. North America 0.35% 5. Africa 0.48% 6. Asia 0.50%

Someone in Asia is five times more likely to be using IE8 than someone in Oceania.

I looked more closely into the Asian stats, noting the proportion of IE8 usage for each country. There’s a very clear top six countries for IE8 usage, after which the figures drop down to be comparable with the world average:

1. Iran 3.99% 2. China 1.99% 3. North Korea 1.38% 4. Turkmenistan 1.31% 5. Afghanistan 1.27% 6. Cambodia 1.05% 7. Yemen 0.63% 8. Taiwan 0.62% 9. Pakistan 0.57% 10. Bangladesh 0.54%

This data is summarized in the map below:

Iran, Turkmenistan and Afghanistan in the Middle East, and China, North Korea & Cambodia in the Far East stand out for their IE8 usage. (Large preview)

Incredibly, IE8 makes up around 4% of desktop users in Iran — forty times the proportion of IE8 users in Oceania.

Next, I looked at the country stats for Africa, as it had around the same overall IE8 usage as Asia. There was a clear winner (Eritrea), followed by a number of countries above or around the 1% usage mark:

1. Eritrea 3.24% 2. Botswana 1.37% 3. Sudan & South Sudan 1.33% 4. Niger 1.29% 5. Mozambique 1.19% 6. Mauritania 1.18% 7. Guinea 1.12% 8. Democratic Republic of the Congo 1.07% 9. Zambia 0.94%

This is summarized in the map below:

Eritrea stands out for its IE8 usage (3.24%). A number of other countries also have >1% usage. (Large preview)

Whereas the countries in Asia that have higher-than-normal IE8 usage are roughly batched together geographically, there doesn’t appear to be a pattern in Africa. The only pattern I can see — unless it’s a coincidence — is that a number of the world’s largest IE8 using countries famously censor internet access, and therefore probably don’t encourage or allow updating to more secure browsers.

If your site is aimed at a purely Western audience, you’re unlikely to care much about IE8. If, however, you have a burgeoning Asian or African market — and particularly if you care about users in China, Iran or Eritrea — you might very well care about your website’s IE8 experience. Yes — even in 2019!

Who’s Still Using IE?

So, who are these people? Do they really walk among us?!

Whoever they are, you can bet they’re not using an old browser just to annoy you. Nobody deliberately chooses a worse browsing experience.

Someone might be using an old browser due to the following reasons:

  • Lack of awareness They simply aren’t aware that they’re using outdated technology.
  • Lack of education They don’t know the upgrade options and alternative browsers open to them.
  • Lack of planning Dismissing upgrade prompts because they’re busy, but not having the foresight to upgrade during quieter periods.
  • Aversion to change The last time they upgraded their software, they had to learn a new UI. “If it ain’t broke, don’t fix it.”
  • Aversion to risk The last time they upgraded, their machine slowed to a crawl, or they lost their favorite feature.
  • Software limitation Their OS is too old to let them upgrade, or their admin privileges may be locked down.
  • Hardware limitation Newer browsers are generally more demanding of your hard disk space, memory and CPU.
  • Network limitation A capped data allowance or slow connection mean they don’t want to download 75MB of software.
  • Legal limitation They might be on a corporate machine that only condones the use of one specific browser.

Is it really such a surprise that there are still people around the world who are clinging to IE8?

I decided to put myself in the shoes of one of these anonymous souls, and browse the web for a day using IE8. You can play along at home! Download an “IE8 on Windows 7” Virtual Machine from the Microsoft website, then run it in a virtualizer like VirtualBox.

IE8 VM: Off To A Bad Start

I booted up my IE8 VM, clicked on the Internet Explorer program in anticipation, and this is what I saw:

The first thing I saw was a 404. Great. (Large preview)

Hmm, okay. Looks like the default web page pulled up by IE8 no longer exists. Well, that figures. Microsoft has officially stopped supporting IE8 so why should it make sure the IE8 landing page still works?

I decided to switch to the most widely used site in the world.

Google The Google homepage renders fine in IE8. (Large preview)

It’s a simple site, therefore difficult to get wrong — but to be fair, it’s looking great! I tried searching for something:

Those who have read my previous articles may notice a recurring theme here. (Large preview)

The search worked fine, though the layout looks a bit different to what I’m used to. Then I remembered — I’d seen the same search result layout when I used the Internet for a day with JavaScript turned off.

For reference, here is how the search results look in a modern browser with JavaScript enabled:

Cleaner layout, extra images and meta information, Netflix/Twitter integration. (Large preview)

So, it looks like IE8 gets the no-JS version of Google search. I don’t think this was necessarily a deliberate design decision — it could just be that the JavaScript errored out:

The page tried and failed to run JavaScript. (Large preview)

Still, the end result is fine by me — I got my search results, which is all I wanted.

I clicked through to watch a YouTube video.

YouTube Funky logo, no images for related videos, and unsurprisingly, no video. (Large preview)

There’s quite a lot broken about this page. All to do with little quirks in IE.

The logo, for instance, is zoomed in and cropped. This is down to IE8 not supporting SVG, and what we’re actually seeing is the fallback option provided by YouTube. They’ve applied a background-image CSS property so that in the event of no SVG support, you’ll get an attempt at displaying the logo. Only they seem to have not set the background-size properly, so it’s a little too far zoomed in.

YouTube set a background-img on the logo span, which pulls in a sprite. (Large preview)

For reference, here is the same page in Chrome (see how Chrome renders an SVG instead):

(Large preview)

And what about that Autoplay toggle? It’s rendered like a weird looking checkbox:

Looks like IE8 defaults to a checkbox under the hood. (Large preview)

This appears to be down to use of a custom element (a paper-toggle-button, which is a Material Design element), which IE doesn’t understand:

paper-toggle-button is a custom element. (The screenshot is from Chrome DevTools, alongside how the Autoplay toggle SHOULD render.) (Large preview)

I’m not surprised this hasn’t rendered properly; IE8 doesn’t even cope with the basic semantic markup we use these days. Try using an <aside> or <main> and it will basically render them as divs, but ignoring any styling you apply to them.

To enable HTML5 markup, you have to explicitly tell the browser these elements exist. They can then be styled as normal:

<!--[if lt IE 9]> <script> document.createElement('header'); document.createElement('nav'); document.createElement('section'); document.createElement('article'); document.createElement('aside'); document.createElement('footer'); </script> <![endif]-->

That is wrapped in an IE conditional, by the way. <!--[if lt IE 9]> is a HTML comment to most browsers — and therefore gets skipped — but in IE it is a conditional which only passes “if less than IE 9”, where it executes/renders the DOM nodes within it.

So, the video page was a fail. Visiting YouTube.com directly didn’t fare much better:

At least I had a visible error message this time! (Large preview)

Undeterred, I ignored the warning and tried searching for a video within YouTube’s search bar.

Computer says no. (Large preview)

IE8 traffic is clearly suspicious enough that YouTube didn’t trust that I’m a real user, and decided not to process my search request!

Signing Up To Gmail

If I’m going to spend the day on IE8, I’m going to need an email address. So I go about trying to set up a new one.

First of all, I tried Gmail.

The text isn’t going to pass color contrast standards! (Large preview)

There’s something a bit off about the image and text here. I think it’s down to the fact that IE8 doesn’t support media queries — so it’s trying to show me a mobile image on desktop.

One way you can get around this is to use Sass to generate two stylesheets; one for modern browsers, and one for legacy IE. You can get IE-friendly, mobile-first CSS (see tutorial by Jake Archibald) by using a mixin for your media queries. The mixin “flattens” your legacy IE CSS to treat IE as though it’s always a specific predefined width (e.g. 65em), giving only the relevant CSS for that width. In this case, I’d have seen the correct background-image for my assumed screen size and had a better experience.

Anyway, it didn’t stop me clicking ‘Create an Account’. There were a few differences between how it looked in IE8 and a modern browser:

IE8 is missing the tight layout, and there’s an overlap of text, but otherwise still works. (Large preview)

Whilst promising at first sight, the form was quite buggy to fill in. The ‘label’ doesn’t get out of the way when you start filling in the fields, so your input text is obfuscated:

The labels overlapped the text I was writing. (Large preview)

The markup for this label is actually a <div>, and some clever JS moves the text out of the way when the input is focussed. The JS doesn’t succeed on IE8, so the text stays stubbornly in place.

The ‘label’ is a div which is overlaid on form input using CSS. (Large preview)

After filling in all my details, I hit “Next”, and waited. Nothing happened.

Then I noticed the little yellow warning symbol at the bottom left of my IE window. I clicked it and saw that it was complaining about a JS error:

I got reasonably far, but then the Next button didn’t work. (Large preview)

I gave up on Gmail and turned to MSN.

Signing Up To Hotmail

I was beginning to worry that email might be off-limits for a ten-year-old browser. But when I went to Hotmail, the signup form looked OK — so far so good:

The signup page looked fine. Guessed we’d have more luck with a Microsoft product! (Large preview)

Then I noticed a CAPTCHA. I thought, “There’s no way I’ll get through this…”

I could see and complete the CAPTCHA. (Large preview)

To my surprise, the CAPTCHA worked!

The only quirky thing on the form was some slightly buggy label positioning, but the signup was otherwise seamless:

The label positions were a bit off, but I guessed my last name followed by my surname would be fine. (Large preview)

Does that screenshot look OK to you? Can you spot the deliberate mistake?

The leftmost input should have been my first name, not my surname. When I came back and checked this page later, I clicked on the “First name” label and it applied focus to the leftmost input, which is how I could have checked I was filling in the correct box in the first place. This shows the importance of accessible markup — even without CSS and visual association, I could determine exactly which input box applied to which label (albeit the second time around!).

Anyhow, I was able to complete the sign-up process and was redirected to the MSN homepage, which rendered great.

If any site is going to work in IE8, it will be the Microsoft homepage. (Large preview)

I could even read articles and forget that I was using IE8:

The article works fine. No dodgy sidebars or borked images. (Large preview)

With my email registered, I was ready to go and check out the rest of the Internet!

Facebook

I visited the Facebook site and was immediately redirected to the mobile site:

“You are using a browser that is not supported by Facebook, so we have redirected you to a simpler version to give you the best experience.” (Large preview)

This is a clever fallback tactic, as Facebook need to support a large global audience on low-end mobile devices, so need to provide a basic version of Facebook anyway. Why not offer that same baseline of experience to older desktop browsers?

I tried signing up and was able to make an account. Great! But when I logged into that account, I was treated with suspicion — just like when I searched for things on YouTube — and was faced with a CAPTCHA.

Only this time, it wasn’t so easy.

“Please enter the code below”. Yeah, right. (Large preview)

I tried requesting new codes and refreshing the page several times, but the CAPTCHA image never loaded, so I was effectively locked out of my account.

Oh well. Let’s try some more social media.

Twitter

I visited the Twitter site and had exactly the same mobile redirect experience.

Twitter treats IE8 as a mobile browser, like Facebook does. (Large preview)

But I couldn’t even get as far as registering an account this time:

Your browser is no longer supported. To sign up, please update it. You can still log in to your existing user accounts. (Large preview)

Oddly, Twitter is happy for you to log in, but not for you to register in the first place. I’m not sure why — perhaps it has a similar CAPTCHA scenario on its sign-up pages which won’t work on older browsers. Either way, I’m not going to be able to make a new account.

I felt awkward about logging in with my existing Twitter account. Call me paranoid, but vulnerabilities like the CFR Watering Hole Attack of 2013 — where the mere act of visiting a specific URL in IE8 would install malware to your machine — had me nervous that I might compromise my account.

But, in the interests of education, I persevered (with a temporary new password):

Successfully logged in. I can see tweets! (Large preview)

I could also tweet, albeit using the very basic <textarea>:

You only miss them when they’re gone. (Large preview)

In conclusion, Twitter is basically fine in IE8 — as long as you have an account already!

I’m done with social media for the day. Let’s go check out some news.

BBC News The BBC appears to be loading a mixture of HTTPS and HTTP assets. (Large preview)

The news homepage looks very basic and clunky but basically works — albeit with mixed content security warnings.

Take a look at the logo. As we’ve already seen on YouTube, IE8 doesn’t support SVG, so we require a PNG fallback.

The BBC uses the <image> fallback technique to render a PNG on IE:

IE8 finds the base64 image inside the SVG and renders it. (Large preview)

…and to ignore the PNG when SVG is available:

The image part is ignored and the svg is rendered nicely. (Large preview)

This technique exploits the fact that older browsers used to obey both <image> and <img> tags, and so will ignore the unknown <svg> tag and render the fallback, whereas modern browsers ignore rendering <image> when inside an SVG. Chris Coyier explains the technique in more detail.

I tried viewing an article:

This site is optimised for modern browsers, and does not fully support your browser. (Large preview)

It’s readable. I can see the headline, the navigation, the featured image. But the rest of the article images are missing:

(Large preview)

This is to be expected, and is due to the BBC lazy-loading images. IE8 not being a ‘supported browser’ means it does not get the JavaScript that enables lazy-loading, thus the images never load at all.

Out of interest, I thought I’d see what happens if I try to access the BBC iPlayer:

...not a lot. (Large preview)

And that got me wondering about another streaming service.

Netflix

I was half expecting an empty white page when I loaded up Netflix in IE8. I was pleasantly surprised when I actually saw a decent landing page:

“Join free for a month” call to action, over a composite image of popular titles. (Large preview)

I compared this with the modern Chrome version:

“Watch free for 30 days” call to action, over a composite image of popular titles. (Large preview)

There’s a slightly different call to action (button text) — probably down to multivariate testing rather than what browser I’m on.

What’s different about the render is the centralized text and the semi-transparent black overlay.

The lack of centralized text is because of Netflix’s use of Flexbox for aligning items:

Netflix uses the Flexbox property justify-content: center to align its text. (Large preview)

A text-align: center on this class would probably fix the centering for IE8 (and indeed all old browsers). For maximum browser support, you can follow a CSS fallbacks approach with old ‘safe’ CSS, and then tighten up layouts with more modern CSS for browsers that support it.

The lack of background is due to use of rgba(), which is not supported in IE8 and below.

A background of rgba(0,0,0,.5) is meaningless to older browsers. (Large preview)

Traditionally it’s good to provide CSS fallbacks like so, which show a black background for old browsers but show semi-transparent background for modern browsers:

rgb(0, 0, 0); /* IE8 fallback */ rgba(0, 0, 0, 0.8);

This is a very IE specific fix, however, basically every other browser supports rgba. Moreover, in this case, you’d lose the fancy Netflix tiles altogether, so it would be better to have no background filter at all! The surefire way of ensuring cross-browser support would be to bake the filter into the background image itself. Simple but effective.

Anyway, so far, so good — IE8 actually rendered the homepage pretty well! Am I actually going to be watching Breaking Bad on IE8 today?

My already tentative optimism was immediately shot down when I viewed the sign-in page:

Can you guess which side is IE8 and which is Chrome? (Large preview)

Still, I was able to sign in, and saw a pared-back dashboard (no fancy auto-expanding trailers):

Each programme had a simple hover state with play icon and title. (Large preview)

I clicked on a programme with vague anticipation, but of course, only saw a black screen.

Amazon

Ok, social media and video are out. All that’s left is to go shopping.

I checked out Amazon, and was blown away — it’s almost indistinguishable from the experience you’d get inside a modern browser:

The Amazon homepage looks almost as good on IE8 as it does on any other browser. (Large preview)

I’ve been drawn in by a good homepage before. So let’s click on a product page and see if this is just a fluke.

The product page also looks fantastic (and makes me hungry). (Large preview)

No! The product page looked good too!

Amazon wasn’t the only site that surprised me in its backwards compatibility. Wikipedia looked great, as did the Gov.UK government website. It’s not easy to have a site that doesn’t look like an utter car crash in IE8. Most of my experiences were decidedly less polished…!

It is difficult to read or navigate sky.com on IE8. (Large preview)

But a deprecated warning notice or funky layout wasn’t the worst thing I saw today.

Utterly Broken Sites

Some sites were so broken that I couldn’t even connect to them!

No dice when accessing GitHub. (Large preview)

I wondered if it might be a temporary VM network issue, but it happened every time I refreshed the page, even when coming back to the same site later in the day.

This happened on a few different sites throughout the day, and I eventually concluded that this never affected sites on HTTP — only on HTTPS (but not all HTTPS sites). So, what was the problem?

Using Wireshark to analyze the network traffic, I tried connecting to GitHub again. We can see that the connection failed to establish because of a fatal error, “Description: Protocol Version.”

TLSv1 Alert (Level: Fatal, Description: Protocol Version) (Large preview)

Looking at the default settings in IE8, only TLS 1.0 is enabled — but GitHub dropped support for TLSv1 and TLSv1.1 in February 2018.

Default advanced settings for IE8: TLS 1.0 is checked, TLS 1.1 and 1.2 are unchecked. (Large preview)

I checked the boxes for TLS 1.1 and TLS 1.2, reloaded the page and — voilà! — I was able to view GitHub!

It doesn’t look pretty, but at least I can now see it! (Large preview)

Many thanks to my extremely talented friend Aidan Fewster for helping me debug that issue.

I’m all for backwards compatibility, but this presents an interesting dilemma. According to the PCI Security Standards Council, TLS 1.0 is insecure and should no longer be used. But by forcing TLS 1.1 or higher, some users will invariably be locked out (and not all are likely to be tech-savvy enough to enable TLS 1.2 in their advanced settings).

By allowing older, insecure standards and enabling users to continue to connect to our sites, we’re not helping them — we’re hurting them, by not giving them a reason to move to safer technologies. So how far should you go in supporting older browsers?

How Can I Begin To Support Older Browsers?

When some people think of “supporting older browsers”, they might be thinking of those proprietary old hacks for IE, like that time the BBC had to do some incredibly gnarly things to support iframed content in IE7.

Or they may be thinking of making things work in the Internet Explorer “quirks mode”; an IE-specific mode of operation which renders things very differently to the standards.

But “supporting older browsers” is very different to “hacking it for IE”. I don’t advocate the latter, but we should pragmatically try to do the former. The mantra I try to live by as a web developer is this:

“Optimize for the majority, make an effort for the minority, and never sacrifice security.”

I’m going to move away from the world of IE8 now and talk about general, sustainable solutions for legacy browser support.

There are two broad strategies for supporting older browsers, both beginning with P:

  1. Polyfilling Strive for feature parity for all by filling in the missing browser functionality.
  2. Progressive Enhancement Start from a core experience, then use feature detection to layer on functionality.

These strategies are not mutually exclusive from one another; they can be used in tandem. There are a number of implementation decisions to make in either approach, each with their own nuances, which I’ll cover in more detail below.

Polyfilling

For some websites or web pages, JavaScript is very important for functionality and you simply want to deliver working JavaScript to as many browsers as possible.

There are a number of ways to do this, but first, a history lesson.

A Brief History Of ECMAScript

ECMAScript is a standard, and JavaScript is an implementation of that standard. That means that ES5 is “ECMAScript version 5”, and ES6 is “ECMAScript version 6”. Confusingly, ES2015 is the same as ES6.

ES6 was the popularized name of that version prior to its release, but ES2015 is the official name, and subsequent ECMAScript versions are all associated with their release year.

Note: This is all helpfully explained by Brandon Morelli in a great blog post that explains the full history of JavaScript versions.

At time of writing, the latest standard is ES2018 (ES9). Most modern browsers support at least ES2015. Almost every browser supports ES5.

Technically IE8 isn’t ES5. It isn’t even ES4 (which doesn’t exist — the project was abandoned). IE8 uses the Microsoft implementation of ECMAScript 3, called JScript. IE8 does have some ES5 support but was released a few months before ES5 standards were published, and so has a mismatch of support.

Transpiling vs Polyfilling

You can write ES5 JavaScript and it will run in almost every ancient browser:

var foo = function () { return 'this is ES5!'; };

You can also continue to write all of your JavaScript like that — to enable backwards compatibility forever. But you’d be missing out on new features and syntactic sugar that has become available in the evolving versions of JavaScript, allowing you to write things like:

const foo = () => { return 'this is ES6!'; };

Try running that JavaScript in an older browser and it will error. We need to transpile the code into an earlier version of JavaScript that the browser will understand (i.e. convert our ES6 code into ES5, using automated tooling).

Now let’s say our code uses a standard ES5 method, such as Array.indexOf. Most browsers have a native implementation of this and will work fine, but IE8 will break. Remember IE8 was released a few months before ES5 standards were published, and so has a mismatch of support? One example of that is the indexOf function, which has been implemented for String but not for Array.

If we try to run the Array.indexOf method in IE8, it will fail. But if we’re already writing in ES5, what else can we do?

We can polyfill the behavior of the missing method. Developers traditionally polyfill each feature that they need by copying and pasting code, or by pulling in external third-party polyfill libraries. Many JavaScript features have good polyfill implementations on their respective Mozilla MDN page, but it’s worth pointing out that there are multiple ways you can polyfill the same feature.

For example, to ensure you can use the Array.indexOf method in IE8, you would copy and paste a polyfill like this:

if (!Array.prototype.indexOf) { Array.prototype.indexOf = (function (Object, max, min) { // big chunk of code that replicates the behaviour in JavaScript goes here! // for full implementation, visit: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexof#Polyfill })(Object, Math.max, Math.min); }

So long as you call the polyfill before you pull in any of your own JS, and provided you don’t use any ES5 JavaScript feature other than Array.indexOf, your page would work in IE8.

Polyfills can be used to plug all sorts of missing functionality. For example, there are polyfills for enabling CSS3 selectors such as :last-child (unsupported in IE8) or the placeholder attribute (unsupported in IE9).

Polyfills vary in size and effectiveness and sometimes have dependencies on external libraries such as jQuery.

You may also hear of “shims” rather than “polyfills”. Don’t get too hung up on the naming — people use the two terms interchangeably. But technically speaking, a shim is code that intercepts an API call and provides a layer of abstraction. A polyfill is a type of shim, in the browser. It specifically uses JavaScript to retrofit new HTML/CSS/JS features in older browsers.

Summary of the “manually importing polyfills” strategy:

  • ✅ Complete control over choice of polyfills;
  • ✅ Suitable for basic websites;
  • ⚠️ Without additional tooling, you’re forced to write in native ES5 JavaScript;
  • ⚠️ Difficult to micromanage all of your polyfills;
  • ⚠️ Out of the box, all your users will get the polyfills, whether they need them or not.
Babel Polyfill

I’ve talked about transpiling ES6 code down to ES5. You do this using a transpiler, the most popular of which is Babel.

Babel is configurable via a .babelrc file in the root of your project. In it, you point to various Babel plugins and presets. There’s typically one for each syntax transform and browser polyfill you’ll need.

Micromanaging these and keeping them in sync with your browser support list can be a pain, so the standard setup nowadays is to delegate that micromanagement to the @babel/preset-env module. With this setup, you simply give Babel a list of browser versions you want to support, and it does the hard work for you:

{ "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage", "targets": { "chrome": "58", "ie": "11" } } ] ] }

The useBuiltIns configuration option of @babel/preset-env is where the magic happens, in combination with an import "@babel/polyfill" (another module) in the entry point of your application.

  • When omitted, useBuiltIns does nothing. The entirety of @babel/polyfill is included with your app, which is pretty heavy.
  • When set to "entry", it converts the @babel/polyfill import into multiple, smaller imports, importing the minimum polyfills required to polyfill the targeted browsers you’ve listed in your .babelrc (in this example, Chrome 58 and IE 11).
  • Setting to "usage" takes this one step further by doing code analysis and only importing polyfills for features that are actually being used. It’s classed as “experimental” but errs on the side of “polyfill too much” rather than “too little”. In any case, I don’t see how it’s possible that it would create a bigger bundle than "entry" or false, so is a good option to choose (and is the way we’re going at the BBC).

Using Babel, you can transpile and polyfill your JavaScript prior to deploying to production, and target support in a specific minimum baseline of browsers. NB, another popular tool is TypeScript, which has its own transpiler that transpiles to ES3, in theory supporting IE8 out of the box.

Summary of using @babel/preset-env for polyfilling:

  • ✅ Delegate micromanagement of polyfills to a tool;
  • ✅ Automated tool helps prevent inclusion of polyfills you don’t need;
  • ✅ Scales to larger, complex sites;
  • ⚠️ Out of the box, all your users will get the polyfills, whether they need them or not;
  • ⚠️ Difficult to keep sight of exactly what’s being pulled into your application bundle.
Lazy Loading Polyfills With Webpack And Dynamic Imports

It is possible to leverage the new import() proposal to feature-detect and dynamically download polyfills prior to initializing your application. It looks something like this in practice:

import app from './app.js'; const polyfills = []; if (!window.fetch) { polyfills.push(import(/* webpackChunkName: "polyfill-fetch" */ 'whatwg-fetch')); } Promise.all(polyfills) .then(app) .catch((error) => { console.error('Failed fetching polyfills', error); });

This example code is shamelessly copied from the very good article, “Lazy Loading Polyfills With Webpack And Dynamic Imports” that delves into the technique in more detail.

Summary:

  • ✅ Doesn’t bloat modern browsers with unnecessary polyfills;
  • ⚠️ Requires manually managing each polyfill.
polyfill.io

polyfill.io is polyfilling as a service, built by the Financial Times. It works by your page making a single script request to polyfill.io, optionally listing the specific features you need to polyfill. Their server then analyzes the user agent string and populates the script accordingly. This saves you from having to manually provide your own polyfill solutions.

Here is the JavaScript that polyfill.io returns for a request made from IE8:

Lots of JS code to polyfill standard ES5 methods in IE8. (Large preview)

Here’s the same polyfill.io request, but where the request came from modern Chrome:

No JS code, just a JS comment. (Large preview)

All that’s required from your site is a single script call.

Summary:

  • ✅ Ease of inclusion into your web app;
  • ✅ Delegates responsibility of polyfill knowledge to a third party;
  • ⚠️ On the flipside, you’re now reliant on a third-party service;
  • ⚠️ Makes a blocking <script> call, even for modern browsers that don’t need any polyfills.
Progressive Enhancement

Polyfilling is an incredibly useful technique for supporting older browsers, but can be a bloat to web pages and is limited in scope.

The progressive enhancement technique, on the other hand, is a great way of guaranteeing a basic experience for all browsers, whilst retaining full functionality for your users on modern browsers. It should be achievable on most sites.

The principle is this: start from a baseline of HTML (and styling, optional), and “progressively enhance” the page with JavaScript functionality. The benefit is that if the browser is a legacy one, or if the JavaScript is broken at any point in its delivery, your site should still be functional.

The term “progressive enhancement” is often used interchangeably with “unobtrusive JavaScript“. They do mean essentially the same thing, but the latter takes it a little further in that you shouldn’t litter your HTML with lots of attributes, IDs and classes that are only used by your JavaScript.

Cut-The-Mustard

The BBC technique of “cutting the mustard” (CTM) is a tried and tested implementation of progressive enhancement. The principle is that you write a solid baseline experience of HTML, and before downloading any enhancing JavaScript, you check for a minimum level of support. The original implementation checked for the presence of standard HTML5 features:

if ('querySelector' in document && 'localStorage' in window && 'addEventListener' in window) { // Enhance for HTML5 browsers }

As new features come out and older browsers become increasingly antiquated, our cuts the mustard baseline will change. For instance, new JavaScript syntax such as ES6 arrow functions would mean this inline CTM check fails to even parse in legacy browsers — not even safely executing and failing the CTM check — so may have unexpected side-effects such as breaking other third-party JavaScript (e.g. Google Analytics).

To avoid even attempting to parse untranspiled, modern JS, we can apply this “modern take” on the CTM technique, taken from @snugug’s blog, in which we take advantage of the fact that older browsers don’t understand the type="module" declaration and will safely skip over it. In contrast, modern browsers will ignore <script nomodule> declarations.

<script type="module" src="./mustard.js"></script> <script nomodule src="./no-mustard.js"></script> <!-- Can be done inline too --> <script type="module"> import mustard from './mustard.js'; </script> <script nomodule type="text/javascript"> console.log('No Mustard!'); </script>

This approach is a good one, provided you’re happy treating ES6 browsers as your new minimum baseline for functionality (~92% of global browsers at the time of writing).

However, just as the world of JavaScript is evolving, so is the world of CSS. Now that we have Grid, Flexbox, CSS variables and the like (each with a varying efficacy of fallback), there’s no telling what combination of CSS support an old browser might have that might lead to a mishmash of “modern” and “legacy” styling, the result of which looks broken. Therefore, sites are increasingly choosing to CTM their styling, so now HTML is the core baseline, and both CSS and JS are treated as enhancements.

The JavaScript-based CTM techniques we’ve seen so far have a couple of downsides if you use the presence of JavaScript to apply CSS in any way:

  1. Inline JavaScript is blocking. Browsers must download, parse and execute your JavaScript before you get any styling. Therefore, users may see a flash of unstyled text.
  2. Some users may have modern browsers, but choose to disable JavaScript. A JavaScript-based CTM prevents them from getting a styled site even when they’re perfectly capable of getting it.

The ‘ultimate’ approach is to use CSS media queries as your cuts-the-mustard litmus test. This “CSSCTM” technique is actively in use on sites such as Springer Nature.

<head> <!-- CSS-based cuts-the-mustard --> <!-- IMPORTANT: the JS depends on having this rule somewhere in the CSS: `body { clear: both }` --> <link rel="stylesheet" href="mq-test.css" media="only screen and (min-resolution: 0.1dpcm), only screen and (-webkit-min-device-pixel-ratio:0) and (min-color-index:0)"> </head> <body> <!-- content here... --> <script> (function () { // wrap in an IIFE to prevent global scope pollution function isSupported () { var val = ''; if (window.getComputedStyle) { val = window.getComputedStyle(document.body, null).getPropertyValue('clear'); } else if (document.body.currentStyle) { val = document.body.currentStyle.clear; } if (val === 'both') { // references the `body { clear: both; }` in the CSS return true; } return false; } if (isSupported()) { // Load or run JavaScript for supported browsers here. } })(); </script> </body>

This approach is quite brittle — accidentally overriding the clear property on your body selector would ‘break’ your site — but it does offer the best performance. This particular implementation uses media queries that are only supported in at least IE 9, iOS 7 and Android 4.4, which is quite a sensible modern baseline.

“Cuts the mustard”, in all its various guises, accomplishes two main principles:

  1. Widespread user support;
  2. Efficiently applied dev effort.

It’s simply not possible for sites to accommodate every single browser / operating system / network connection / user configuration combination. Techniques such as cuts-the-mustard help to rationalize browsers into C-grade and A-grade browsers, according to the Graded Browser Support model by Yahoo!.

Cuts-The-Mustard: An Anti-Pattern?

There is an argument that applying a global, binary decision of “core” vs “advanced” is not the best possible experience for our users. It provides sanity to an otherwise daunting technical problem, but what if a browser supports 90% of the features in your global CTM test, and this specific page doesn’t even make use of the 10% of the features it fails on? In this case, the user would get the core experience, since the CTM check would have failed. But we could have given them the full experience.

And what about cases where the given page does make use of a feature the browser doesn’t support? Well, in the move towards componentization, we could have a feature-specific fallback (or error boundary), rather than a page-level fallback.

We do this every day in our web development. Think of pulling in a web font; different browsers have different levels of font support. What do we do? We provide a few font file variations and let the browser decide which to download:

@font-face { font-family: FontName; src: url('path/filename.eot'); src: url('path/filename.eot?#iefix') format('embedded-opentype'), url('path/filename.woff2') format('woff2'), url('path/filename.woff') format('woff'), url('path/filename.ttf') format('truetype'); }

We have a similar fallback with HTML5 video. Modern browsers will choose which video format they want to use, whereas legacy browsers that don’t understand what a <video> element is will simply render the fallback text:

<video width="400" controls> <source src="mov_bbb.mp4" type="video/mp4"> <source src="mov_bbb.ogg" type="video/ogg"> Your browser does not support HTML5 video. </video>

The nesting approach we saw earlier used by the BBC for PNG fallbacks for SVG is the basis for the <picture> responsive image element. Modern browsers will render the best fitting image based on the media attribute supplied, whereas legacy browsers that don’t understand what a <picture> element is will render the <img> fallback.

<picture> <source media="(min-width: 650px)" srcset="img_pink_flowers.jpg"> <source media="(min-width: 465px)" srcset="img_white_flower.jpg"> <img src="img_orange_flowers.jpg" alt="Flowers" style="width:auto;"> </picture>

The HTML spec has carefully evolved over the years to provide a basic fallback mechanism for all browsers, whilst allowing features and optimisations for the modern browsers that understand them.

We could apply a similar principle to our JavaScript code. Imagine a Feature like so, where the foo method contains some complex JS:

class Feature { browserSupported() { return ('querySelector' in document); // internal cuts-the-mustard goes here } foo() { // etc } } export default new Feature();

Before calling foo, we check if the Feature is supported in this browser by calling its browserSupported method. If it’s not supported, we don’t even attempt to call the code that would otherwise have errored our page.

import Feature from './feature'; if (Feature.browserSupported()) { Feature.foo(); }

This technique means we can avoid pulling in polyfills and just go with what’s natively supported by each individual browser, gracefully degrading individual features if unsupported.

Note that in the example above, I’m assuming the code gets transpiled to ES5 so that the syntax is understood by all browsers, but I’m not assuming that any of the code is polyfilled. If we wanted to avoid transpiling the code, we could apply the same principle but using the type="module" take on cuts-the-mustard, but it comes with the caveat that it already has a minimum ES6 browser requirement, so is only likely to start being a good solution in a couple of years:

<script type="module"> import Feature from './feature.js'; if (Feature.browserSupported()) { Feature.foo(); } </script>

We’ve covered HTML, and we’ve covered JavaScript. We can apply localized fallbacks in CSS too; there’s a @supports keyword in CSS, which allows you to conditionally apply CSS based on the presence or absence of support for a CSS feature. However, it is ironically caveated with the fact that it is not universally supported. It just needs careful application; there’s a great Mozilla blog post on how to use feature queries in CSS.

In an ideal world, we shouldn’t need a global cuts-the-mustard check. Instead, each individual HTML, JS or CSS feature should be self-contained and have its own error boundaries. In a world of web components, shadow DOM and custom elements, I expect we’ll see more of a shift to this sort of approach. But it does make it much more difficult to predict and to test your site as a whole, and there may be unintended side-effects if, say, the styling of one component affects the layout of another.

Two Main Backwards Compatibility Strategies

A summary of polyfilling as a strategy:

  • ✅ Can deliver client-side JS functionality to most users.
  • ✅ Can be easier to code when delegating the problem of backwards-compatibility to a polyfill.
  • ⚠️ Depending on implementation, could be detrimental to performance for users who don’t need polyfills.
  • ⚠️ Depending on complexity of application and age of browser, may require lots of polyfills, and therefore run very poorly. We risk shipping megabytes of polyfills to the very browsers least prepared to accept it.

A summary of progressive enhancement as a strategy:

  • ✅ Traditional CTM makes it easy to segment your code, and to manually test.
  • ✅ Guaranteed baseline of experience for all users.
  • ⚠️ Might unnecessarily deliver the core experience to users who could handle the advanced experience.
  • ⚠️ Not well suited to sites that require client-side JS for functionality.
  • ⚠️ Sometimes difficult to balance a robust progressive enhancement strategy with a performant first render. There’s a risk of over-prioritizing the ‘core’ experience to the detriment of the 90% of your users who get the ‘full’ experience (e.g. providing small images for noJS and then replacing with high-res images on lazy-load means we’ve wasted a lot of download capacity on assets that are never even viewed).
Conclusion

IE8 was once a cutting edge browser. (No, seriously.) The same could be said for Chrome and Firefox today.

If today’s websites are totally unusable in IE8, the websites in ten years time’ are likely to be about as unusable in today’s modern browsers — despite being built upon the open technologies of HTML, CSS, and JavaScript.

Stop and think about that for a moment. Isn’t it a bit scary? (That said, if you can’t abandon browsers after ten years and after the company who built it has deprecated it, when can you?)

IE8 is today’s scapegoat. Tomorrow it’ll be IE9, next year it’ll be Safari, a year later it might be Chrome. You can swap IE8 out for ‘old browser of choice’. The point is, there will always be some divide between what browsers developers build for, and what browsers people are using. We should stop scoffing at that and start investing in robust, inclusive engineering solutions. The side effects of these strategies tend to pay dividends in terms of accessibility, performance and network resilience, so there’s a bigger picture at play here.

We tend not to think about screen reader numbers. We simply take it for granted that it’s morally right to do our best to support users who have no other way of consuming our content, through no fault of our own. The same principle applies to people using older browsers.

We’ve covered some high-level strategies for building robust sites that should continue to work, to some degree, across a broad spectrum of legacy and modern browsers.

Once again, a disclaimer: don’t hack things for IE. That would be missing the point. But be mindful that all sorts of people use all sorts of browsers for all sorts of reasons, and that there are some solid engineering approaches we can take to make the web accessible for everyone.

Optimize for the majority, make an effort for the minority, and never sacrifice security.

Further Reading on SmashingMag: (ra, il)
Categories: Around The Web

A Detailed Comparison Between WordPress And October CMS

Smashing Magazine - Mon, 03/18/2019 - 7:00am
A Detailed Comparison Between WordPress And October CMS A Detailed Comparison Between WordPress And October CMS Leonardo Losoviz 2019-03-18T12:00:28+01:00 2019-03-23T22:35:13+00:00

Three months ago, WordPress finally released React-powered Gutenberg to power its default content editing experience, triggering many people who are not happy with this change to look for alternatives. Some folks decided to fork and release pre-Gutenberg WordPress, however, for me this doesn’t make much sense since it still carries 15 years worth of technical debt. If I were to find an alternative to WordPress, I would try to avoid being stuck in the past, and aim for a clean cut through some mature platform built on modern foundations.

This article compares WordPress to the arguably similar yet more modern October CMS on a wide arrange of both technical and non-technical topics. The goal of the article is not to convince people to stick to WordPress or to switch to October CMS, but simply to demonstrate what aspects must be taken into account before concluding the move to a different platform. The same comparison could (and should) also be done with other platforms before making a sensible decision.

Why October CMS

I found out about October CMS when it won an award, after which I went into research mode and spent a good deal of time digging deep into this CMS — from the perspective of both a user and a developer. As I gained knowledge on this CMS, I felt confident that I could provide an objective evaluation of its features as contrasted to WordPress. I chose this CMS for the comparison over alternative options such as Grav, Statamic, ButterCMS, Joomla, Drupal, Jekyll, Hugo, and others, for the following reasons:

  • I know how this CMS works (unlike Grav);
  • It is free and open source (unlike Statamic and ButterCMS);
  • At five years, it is “relatively” new (unlike Joomla and Drupal);
  • It is a dynamic (not static) content generator and based in PHP (unlike Jekyll and Hugo).

I believe that October CMS is a good candidate because it is based on Laravel which is a framework used for building modern applications. After seven years of existence, it has received positive approval from developers (as evidenced by its sizeable community and ecosystem), and marks a distinct contrast over coding in WordPress, i.e. WordPress is mostly procedural programming while Laravel is decidedly object-oriented programming.

What’s The Difference Between The Two?

Below I will compare WordPress and October CMS on different categories and highlight what, I believe, is good and not so good about them. However, I will not pick a winner, since that’s not the objective of the article and, in any case, there is no “best” or even “better” CMS: each CMS has its own set of strengths and weaknesses that will make it more or less suitable for each task, project, company, team, and anything else. Moreover, a project may benefit from using more than one CMS, such as using some CMS to manage and provide data, and another CMS to render the view. To decide which of the dozens of CMSs out there is most suitable for your own needs is entirely up to you.

In addition, this article could never draw definitive conclusions since it is only concerned with a subset of all possibilities. For instance, we can also find online comparisons such as “WordPress vs Drupal vs Joomla”, “WordPress vs Static Site Generators” and even “WordPress vs Medium”. Because none of these articles sees the full picture, then none of these comparisons can ever be conclusive, and should not be treated as such.

Let’s start with the comparison.

Philosophy And Target Group

It is no coincidence that WordPress powers nearly 1 in 3 websites. Ever since its inception, it has strived to be extremely user-friendly and has done so successfully, removing friction for technical and non-technical users alike as well as for people from all backgrounds — irrespective of their education and economic levels. WordPress’ founder Matt Mullenweg expressed that WordPress’ motto of “Democratize Publishing” for the current era meant the following:

“People of all backgrounds, interests, and abilities should be able to access Free-as-in-speech software that empowers them to express themselves on the open web and to own their content.”

WordPress is easy to use for everyone and its inclusivity is evidenced on the development side too: It’s not uncommon to find people without a programming background (such as marketers, designers, bloggers, sales people, and others) tinkering with their WordPress installations, designing their own themes and successfully launching their own websites. WordPress is user-centric, and the needs of the users trump those of the developers. In WordPress, the user is king (or queen).

In contrast, October CMS is more geared towards the developer, as explicity established from its very first release:

“October makes one bold but obvious assumption: clients don’t build websites, developers do. The role of a client is to manage the website and convey their business requirements. The web developer, and the industry itself, revolves around mediating these factors.”

In the words of its founders, the CMS’ mission is to “prove that making websites is not rocket science.” Being based on Laravel, October CMS can claim to have strong foundations of reusable, modular code that can produce properly-architected applications, maintainable in the long term and fully customizable without requiring hacks — the type which attracts serious programmers. October CMS can also provide a great user experience, however, it is not as simple or frictionless as that provided by WordPress. Users may need to be explained how to use certain functionality before being able to use it. For instance, embedding a form from some plugin has a lengthy explanation on how to do it, which is more cumbersome than the self-evident, drag-and-drop functionality provided by several form plugins in WordPress.

Installation

WordPress is famous for its 5-minute installation, even though many people point out that (taking into consideration all the plugins that must be installed) a typical installation requires 15 minutes or more. In addition, WordPress also offers the Multisite feature, which allows us to create a network of multiple virtual sites under a single installation. This feature makes it easy for an agency to administer the sites of multiple clients — among other user cases.

Installing October CMS is also very smooth: The Wizard installation itself takes even less than five minutes, and if you install it through the Console installation, it is even faster. You can do the latter by simply navigating to the target directory and then executing curl -s https://octobercms.com/api/installer | php (after which we need to input the database configuration, otherwise it behaves as a flat-file CMS). Once the installation has been completed, we will have a fully functioning website, but still quite bare (if you add the time needed to install and configure the required plugins, you can expect it to take at least 15 minutes).

Installing October CMS with the Wizard is a breeze. (Large preview) Security

WordPress has been accused of being insecure due to the high amount of vulnerabilities that are constantly found. This forces users to have the software for the CMS and all installed plugins always up to date to avoid security exploits. Among the main issues is WordPress’ support for older versions of PHP which are not supported by the PHP development community anymore (WordPress currently supports PHP 5.2.4, while the latest fully supported PHP version is 5.6). However, this problem should be resolved in April 2019 when WordPress will officially start supporting PHP versions 5.6 and upwards.

Otherwise, WordPress is not necessarily insecure because of itself, but because of its high popularity, which makes it a primal target for hackers. However, this plays both ways: WordPress ubiquity means that its security team must really take their job seriously by constantly looking for exploits and fixing them as soon as possible, otherwise up to a third of the web is at risk. The stakes are just too high.

October CMS, on the other hand, doesn’t have a reputation of being insecure. However, since there are roughly 27,000 live sites that use October as compared with WordPress’ millions, we can’t judge the two of them on the same terms. Nevertheless, the team behind October CMS does take security seriously, as evidenced by the Wizard installation’s prompt to input the CMS backend URL, set as /backend by default but changeable to anything else, as to make it more difficult for hackers to target the site. In contrast, changing WordPress’ login and backend URLs from /wp-login.php and /wp-admin respectively to something else must be done through a plugin. In addition, October CMS can function as a flat-file CMS (i.e. without a database) and avoid database-related vulnerabilities such as SQL injection.

Technology Stack

Both WordPress and October CMS run on the traditional LAMP stack: Linux, Apache, MySQL, and PHP. (However, only PHP is fixed: we can also use Windows, Nginx, MariaDB, and others.) October CMS can also behave as a flat-file CMS, meaning that it can do without a database, however, at the cost of forgoing many functionalities (such as blog posts and users) the only functionality that is guaranteed is pages, which is considered to be the basic unit for the creation and publishing of content and shipped as a core feature.

Concerning the language stack, sites built with both WordPress and October CMS are based on HTML, CSS, and JavaScript (note that PHP is used to generate the HTML). October CMS also makes it easy to use LESS and SASS files.

Programming Paradigm

WordPress follows a functional programming paradigm, based on calculating computations by calling functions devoid of application state. Even though WordPress developers do not need to stick to functional programming (for instance, for coding their themes and plugins), the WordPress core code inherits this paradigm from 15 years of preserving backwards compatibility, which has been one of the pillars to WordPress’ success but which has the unintended consequence of accumulating technical debt.

On the other side, October CMS follows an imperative programming paradigm, based on calculating computations by manipulating objects’ state. October CMS sits on top of Laravel, a web framework fully founded on Object-Oriented Programming principles that enable the production of modular applications based on concepts such as the Model-View-Controller to decouple the user interface from the application data, Dependency Injection to configure class dependencies, and the Interface Segregation Principle to define the core services provided by the framework, among many others.

Hooks/Events

Programming in WordPress could be characterized as HDD which stands for “Hook-Driven Development”. A hook is a mechanism that allows changing a default behavior or value and allowing other code to execute related functionality. Hooks are triggered through “actions” which allow executing extra functionality, and “filters” that allow modifying values.

Hooks, which are widespread across the WordPress codebase, are one of the concepts that I most like from coding in WordPress. They allow plugins to interact with other plugins (or with a core or theme) in a clean way, providing some basic support of Aspect-Oriented Programming.

Good news is that Laravel (and in consequence October CMS) also supports the concept of hooks, which is called “events”. Events provide a simple observer implementation, enabling code to subscribe and listen for events that occur in the application and react as needed. Events make it possible to split a complex functionality into components, which can be installed independently yet collaborate with each other, thus enabling the creation of modular applications.

Dependence on JavaScript Libraries

The latest version of WordPress incorporates React-powered Gutenberg for its default content creation experience. Hence, WordPress development now relies by and large on JavaScript (predominantly through React), even though it is also possible to use other frameworks or libraries (as evidenced by Elementor Blocks for Gutenberg which is based on Marionette). In addition, WordPress still relies on Backbone.js (for the Media Manager) and jQuery (legacy code), however, we can expect the dependence on these libraries to wither away as Gutenberg is consolidated as the new norm.

October CMS depends on jQuery, which it uses to implement its optional AJAX framework to load data from the server without a browser page refresh.

Pages, Themes and Plugins

Both WordPress and October CMS treat a page as the basic unit for creating and publishing content (in WordPress case, in addition to the post), support changing the site’s look and feel through themes, and allow to install and extend the site’s functionalities through plugins. Even though the concepts are the same in both CMSs, there are a few differences in implementation that produce somewhat different behavior.

In WordPress, pages are defined as content and stored in the database. As a result, page content can be created through the CMS only (e.g. in the dashboard area), and switching from one theme to another doesn’t make an existing page become unavailable. This produces an overall frictionless experience.

In October CMS, on the other hand, pages are static files stored under the theme directory. On the positive side from this architectural decision, page content can be created from an external application, such as text editors like Sublime or Visual Studio Code. On the negative side, when switching from one theme to another, it is required to manually recreate or copy the pages from the current to the new theme, or otherwise, they will disappear.

Significantly, October CMS resolves routing through pages, hence pages are used not just as containers for content but also for functionality. For instance, a plugin for blogging depends on a page for displaying the list of blog posts under a chosen URL, another page to display a single blog post under another chosen URL, and so on. If any of these pages disappear, the associated functionality from the plugin becomes unavailable, and that URL will produce a 404. Hence, in October CMS themes and plugins are not thoroughly decoupled, and switching themes must be done carefully.

October CMS enables the creation of content from external applications. (Large preview) Core vs Plugin Functionality

WordPress attempts to deliver a minimal core functionality which is enhanced through plugins. WordPress relies on the “80⁄20 rule” to decide if to include some functionality in its core experience or not. If it benefits 80% of the users it goes in, otherwise, it belongs to plugin-land. When adding plugins to a site, they can lead to bloat if too many plugins are installed. Plugins may also not work well with one another, or execute similar code or load similar assets, resulting in suboptimal performance. Hence, whereas launching a WordPress site is relatively easy, a bigger challenge is its general maintenance and being able to preserve an optimal and performant state when adding new features.

The WordPress plugin directory claims to have almost 55,000 plugins. (Large preview)

Likewise, October CMS also attempts to deliver a minimal core functionality, but on steroids: the only guaranteed functionality is the creation and publication of pages, and for everything else we will need to install one plugin or another, which is expressed as:

“Everything you need, and nothing you don't.”

The objective is clear: most simple sites are only composed of pages, with possibly no blog posts, users or login area. So why should the application load resources for these when they are not needed? As a consequence, functionalities for blogging, user management, translation and several others are released through the plugin directory.

Searching for 'Rainlab' in October’s plugins directory displays plugins created by October CMS' team. (Large preview)

October CMS also includes certain features in its core which (even though they are not always needed) can enhance the application significantly. For instance, it provides out-of-the-box support to upload media files to Amazon S3 and accesses them through the Rackspace CDN. It also includes a Media Manager which is mostly used through plugins, e.g. for adding images into a blog post. (Pages can also use the Media Manager to embed media files, however, the CMS also ships with an Assets section to upload media files for these which seems more suitable.)

I believe that October’s opinionatedness can perfectly enable us to produce an application that is as lean as possible — mostly concerning simple sites. However, it can also backfire and encourage bloat, because the line of what is needed and what is not is an arbitrary one, and it’s difficult to be set in advance by the CMS. This difficulty can be appreciated when considering the concept of a “user”: In WordPress, website users and website admins belong to the same user entity (and through roles and privileges we can make a user become an admin). In October CMS, these two are implemented separately, shipping in core the implementation for the website administrator which can log in to the backend area and modify the settings, and through a plugin the implementation of the website user. These two types of users have a different login process and a different database table for storing their data, thus arguably breaching the DRY (Don’t Repeat Yourself) principle.

This problem arises not only concerning the behavior of an entity but also what data fields it must contain. For instance, should the website user data fields be predefined? Is a telephone field required? What about an Instagram URL field, considering that Instagram got kind of cool only recently? But then, when building a professional website shouldn’t we use a LinkedIn URL field instead? These decisions clearly depend on the application and can’t be decided by either CMS or plugin.

The October CMS plugin called User implements users but without any user field, on top of which plugin User Plus adds several arbitrary user fields, which are possibly not enough, so plugin User Plus+ adds yet other user fields. When, where and how do we stop this process?

Another problem is when there is no room to add new capabilities to an entity, which leads to the creation of another, extremely similar entity, just to support those required capabilities. For instance, October CMS ships with pages, and allows to create “static pages” through a plugin. Their nature is the same: both pages and static pages are saved as static files. The only difference between them (as far as I can tell) is that static pages are edited with a visual editor instead of the HTML editor, and can be added to menus. In my opinion, only structural differences, such as having one entity saved as a static file and the other one stored in the database, could justify creating a second entity for a page (there is a pull request to do this), but for simple features, as is the case currently, it constitutes development bloat.

In summary, a well implemented October CMS application can be very lean and efficient (e.g. by removing the database when not needed), but on the contrary it can also become unnecessarily bloated, forcing developers to implement several solutions for similar entities, and which can be very confusing to use (“Should I use a page or a static page?”). Because neither WordPress or October CMS has found a perfect solution for removing bloat, we must design either application architecture with care to avoid down-the-road pain.

Content Creation

Gutenberg makes two important contributions to WordPress: It uses components as the unit for building sites (which offers several advantages over coding blobs of HTML), and it introduces an entity called a “block” which, once Gutenberg Phase 2 is completed (presumably in 2019), will provide a unified way to incorporate content into the site, thus enabling a simpler user experience as opposed to the more chaotic process of adding content through shortcodes, TinyMCE buttons, menus, widgets, and others.

Since WordPress 5.0 Gutenberg is the default content creation experience. (Large preview)

Because Gutenberg blocks can produce and save static HTML as part of the blog post, then installing many Gutenberg blocks doesn’t necessarily translate into bloat on the website on the user side, but can be kept restricted to the admin side. Hence, Gutenberg can arguably be considered a good approach to produce websites in a modular way, with a simple yet powerful user experience for creating content. Possibly the biggest drawback is the (unavoidable, but not easily so) requirement to learn React, whose learning curve is rather steep.

If React components are the basic unit for creating content in WordPress, October CMS is based on the premise that knowing good old HTML is enough for building sites. Indeed, when creating a page, we are simply presented an HTML (Markup) editor:

Creating a page in October CMS. (Large preview)

If the page were solely static HTML, then there would be no need for a CMS. Instead, October CMS pages are written using Twig templates which are compiled to plain optimized PHP code. They can select a layout to include the scaffolding of the page (i.e. repetitive elements, such as the header, footer, and so on), can implement placeholders, which are defined on the layout to allow the page to customize content, and can include partials, which are reusable chunks of code. In addition, pages can include content blocks, which are either text, HTML or Markdown files that can be edited on their own and can attach components which are functionalities implemented through plugins. And finally, for whenever HTML is not enough and we need to produce dynamic code, we can add PHP functions.

The editor is all about HTML. There is no TinyMCE textarea for adding content in a visual manner — at least not through the default experience (this functionality belongs to plugin-land). Hence, having knowledge of HTML could be considered a must for using October CMS. In addition, the several different inputs for creating content (pages, layouts, placeholders, partials, content blocks, components, and PHP functions) may be very effective, however, it is certainly not as simple as through the unified block interface from WordPress. It can even get more complex since other elements can also be added (such as static pages and menus, and snippets), and some of them, such as pages and static pages, seemingly provide the same functionality, making it confusing to decide when to use one or the other.

As a result, I dare say that while pretty much anyone can use a WordPress site from the admin side, October CMS is more developer-friendly than non-technical user-friendly, so programmers may find it a joy to use, but certain other roles (marketers, sales people, and the like) may find it non-intuitive.

Media Manager

Both WordPress and October CMS are shipped with a Media Manager which allows adding media files to the site effortlessly, supporting the addition of multiple files simultaneously through a drag-and-drop interface and displaying the images within the content area. They look and behave similarly; the only notable differences I found are that WordPress’ Media Manager allows to embed image galleries, and October’s Media Manager allows to manually create a folder structure where to place the uploaded files.

October CMS ships with a powerful Media Manager. (Large preview)

Since the introduction of Gutenberg, though, WordPress’ media capabilities have been enhanced greatly, enabling to embed videos, pictures and photo galleries in place as compared to within a TinyMCE textarea (which only provides a non-accurate version of how it will look like in the site), and unlocking powerful, yet easy-to-use features as shown in this video.

Internationalization

WordPress core uses gettext to enable the translation of themes and plugins. Starting from a .pot file containing all strings to translate, we need to create a .po file containing their translation to the corresponding language/locale, and this file is then compiled to a binary .mo file suitable for fast translation extraction. Tools to perform these tasks include GlotPress (online) and Poedit (downloadable application). Conveniently, this mechanism also works for client-side localization for Gutenberg.

Poedit allows to translate strings for themes and plugins for WordPress. (Large preview)

WordPress currently doesn’t ship any solution in core to translate content, and will not do so until Phase 4 of Gutenberg (targeted for year 2020+). Until then, this functionality is provided by plugins which offer different strategies for storing and managing the translated content. For example, while plugins such as Polylang and WPML store each translation on its own row from a custom database table (which is clean since it doesn’t mix content together, but slower since it requires an additional INNER JOIN of two tables when querying the database), plugin qTranslate X stores all translations on the same field from the original database table (faster for querying the data, but content mixed all together can produce wreckage on the site if disabling the plugin). Hence, we can shop around and decide the most suitable strategy for our needs.

October CMS doesn’t ship the multilingual functionality through its core, but as a plugin created by the October CMS team that guarantees a faultless integration into the system. From a functional point of view, this plugin delivers what it promises. From a development point of view, it is not quite ideal how this plugin actually works. In WordPress, a page is simply a post with post type “page” and there is a single translation mechanism for them, but in October CMS, there are entities “page”, “static page” and “blog post” and, even though quite similar, they require three different implementations for their translations! Then, the content from a “page” can include message codes (e.g. codes called nav.content, header.title, and so on), each of which contains its translations for all locales as a serialized JSON object in database table rainlab_translate_messages. The content from a “static page” is created into a new static file per locale, however, all translated URLs for all locales are stored not in their corresponding file but instead on the default language’s file. The content for the “blog post” is stored as a serialized JSON object with one row per locale in database table rainlab_translate_attributes and the translated URL is stored with one row per locale in database table rainlab_translate_indexes. I don’t know if this complexity is due to how the plugin was implemented or whether it is due to October CMS’ architecture. Whichever the case, this is another instance of undesired bloat on the development side.

Plugin Management

Both WordPress and October CMS offer a sophisticated plugin manager which allows to search for plugins, install new plugins, and update currently-installed plugins to their latest version — all from within the backend.

October CMS enables to keep all plugins up-to-date effortlessly. (Large preview) Dependency Management

October CMS uses Composer as the package manager of choice, enabling plugins to download and install their dependencies when being installed, thus delivering a painless experience.

WordPress, on the opposite side, hasn’t officially adopted Composer (or any PHP dependency manager) because the community can’t agree if WordPress is a site or a site dependency. Hence, if they require Composer for their projects, developers must add it on their own. With the switch to Gutenberg, npm has become the preferred JavaScript dependency manager, with a popular developer toolkit depending on it, and the client-side libraries being steadily released as autonomous packages in the npm registry.

Interaction With The Database

WordPress provides functions to retrieve database data (such as get_posts) and store it (such as wp_insert_post and wp_update_post). When retrieving data, we can pass parameters to filter, limit and order the results, in order to indicate if the result must be passed as an instance of a class or as an array of properties and others. When the function doesn’t fully satisfy our requirements (e.g. when we need to do an INNER JOIN with a custom table) then we can query the database directly through global variable $wpdb. When creating a plugin with a custom post type, the code will most likely be executing custom SQL queries to retrieve and/or save data into custom tables. In summary, WordPress attempts to provide access to the database through generic functions in the first stage, and through low-level access to the database in the second stage.

October CMS employs a different approach: Instead of connecting to the database straight away, the application can use Laravel’s Eloquent ORM to access and manipulate database data through instances of classes called Models, making the interaction with the database also be based on Object-Oriented Programming. It is high-level access; just by following the rules on how to create tables and set-up relationships among entities, a plugin can retrieve and/or save data without writing a line of SQL. For instance, the code below retrieves an object from the database through model Flight, modifies a property, and stores it again:

$flight = Flight::find(1); $flight->name = 'Darwin to Adelaide'; $flight->save(); Upgrading The Data Model

Another reason for WordPress’ success (in addition to not breaking backward compatibility) has been its database architecture, which was engineered to enable applications to grow over time. This objective is accomplished through “meta” properties, i.e. properties that can be loosely added to a database object at any moment. These properties are not stored in a column from the corresponding entity table (either wp_posts, wp_users, wp_comments or wp_terms), but instead as a row in the corresponding “meta” table (wp_postmeta, wp_usermeta, wp_commentmeta or wp_termmeta) and retrieved doing an INNER JOIN. Hence, even though retrieving these meta values is slower, they provide unlimited flexibility, and the application’s data model rarely needs to be re-architected from scratch in order to implement some new functionality.

WordPress provides unlimited flexibility for upgrading the application’s data model. (Large preview)

October CMS doesn’t use meta properties but instead can store several arbitrary values, which are not directly mapped as columns in the database tables, as a serialized JSON object. Otherwise, when an object needs some new property, we need to add a new column on the corresponding table (which is the reason behind plugins User Plus and User Plus+, mentioned earlier on). To update the application’s database schema, October CMS relies on Laravel’s Migrations, which are sets of instructions to execute against the schema (such as add or drop a column, rename an index, etc) and which are executed when upgrading the software (e.g. when installing a plugin’s new version).

Headless Capabilities

Both WordPress and October CMS can be used as headless, i.e. treating the CMS as a content management system that makes content accessible through APIs, which allows to render the website on the client-side and can power other applications (such as mobile apps). Indeed, WordPress is steadily heading towards headless, since the Gutenberg content editor itself treats WordPress as a headless CMS (and, as a consequence, Gutenberg can also work with any other CMS too, as Drupal Gutenberg demonstrates).

A headless system needs to implement some API to return the data, such as REST and GraphQL. WordPress supports REST through WP REST API (merged in core), exposing endpoints under some predefined route /wp-json/wp/v2/...; October CMS supports REST through plugins RESTful and API Generator, which allow to create custom endpoints and, as a consequence, support versioning as part of the endpoint URL and can offer a better security against bots. Concerning GraphQL, WordPress supports it through WPGraphQL, while October CMS currently has no implementations for it.

Quite importantly, a headless system needs to offer powerful content management capabilities. As mentioned earlier on, WordPress has a very solid database architecture, offering a plethora of data entities (users, posts and custom posts, pages, categories, tags and custom taxonomies, comments) over which the application can be reasonably well modelled, meta properties to extend these data entities (enabling the application to upgrade its data model accordingly and without major changes), and with plugin Advanced Custom Fields filling the gap to construct relationships among the data entities. In addition, plugin VersionPress allows to version control the database content using Git. Hence, WordPress is undoubtedly a good fit for managing content, as demonstrated in several projects in the wild.

On its part, and as mentioned earlier on, October CMS can omit the database and behave as a flat-file system, or it can have a database and behave as a hybrid, storing the content from pages as static files and blog posts (and others) on the database. As a consequence, content is not centralized, and its management involves a different approach. For instance, while we can use Git to version control pages, there is no support to version control the database per se; the solution to this is to populate data into the database through Seeders which, being code, can be put under version control and executed upon deployment. In addition, October CMS doesn’t offer a baked-in database model featuring predefined data entities that can support the needs of most applications. Hence, more likely than not the application will need custom development to implement its data model, which means more work, but also means that it can be more efficient (e.g. accessing a property from a column is faster than from a row in another table through an INNER JOIN, which is the case with WordPress’ meta properties).

CLI Support

Both WordPress and October CMS can be interacted with from the console through a Command Line Interface (CLI): WordPress through WP-CLI and October CMS through Laravel’s Artisan. In addition to Laravel’s commands, October CMS implements several custom commands for updating the system, migrating the database, and others. These tools make it very convenient to access the site from outside a browser, for instance for testing purposes.

Managed Hosting

It is not a problem finding a managed hosting provider for a WordPress site: given WordPress’ market share, there are dozens (if not hundreds) of providers out there vying with each other for the business, constituting a very dynamic market. The only problem is finding the most suitable provider for our specific sites based on all of their offerings, which can vary based on price, quality, type (shared or dedicated services), bandwidth and storage size, customer support, location, frequency of renewal of equipment, and other variables which we can navigate mainly through reviews comparing them (such as this one, this one or this one).

Even though nothing near as many as WordPress, October CMS still enjoys the offering from several hosting providers, which allows for some consideration and selection. Many of them are listed as October Partners, and several others are found DuckDuckGoing, but since I haven’t found any independent review of them or article comparing them, the task of finding out the most suitable one will take some effort.

Marketplace, Ecosystem And Cost

WordPress’ commercial ecosystem is estimated to be USD $10 billion/year, evidencing how many people and companies have managed to make a living by offering WordPress products and services, such as the creation of sites, hosting, theme and plugin development, support, security, and others. Indeed, its size is so big it is even bloated, meaning that it is very common to find different plugins solving the same problem, plugins that underdeliver, underperform or have not been updated for years, and themes which seem to look-alike each other. However, when creating a new site, the size and variety of the ecosystem also means that we will most likely find at least one plugin implementing each of the required functionalities, enabling us to save money by not having to develop the functionality ourselves, and the availability of customizable themes enables to produce a reasonably distinctive-looking site with minimal effort. As a consequence, we can easily create and launch a WordPress site for less than USD $100, making WordPress a sensible option for projects of any budget.

Being relatively new (only five years so far), OctoberCMS certainly doesn’t enjoy anything near WordPress’ marketplace and ecosystem sizes, however, it has been growing steadily so its size is bound to become bigger. Currently, its marketplace boasts 600+ plugins, and only a handful of themes. Concerning plugins, the October CMS team is requesting the community to put their effort into the creation of original plugins, delivering functionality not yet provided by any other plugin.

Hence, even though 600+ plugins doesn’t sound like much, at least these translate into 600+ different functionalities. This way, even though it is not possible to choose among several vendors, at least we can expect to have those basic website features (such as blogging, comments, forum, integration with social media, e-commerce, and others) to be covered. Also, since October’s founders are personally reviewing all submitted plugins and judging them according to quality guidelines, we can expect these plugins to perform as expected. As another plus, October plugins can incorporate elements from Laravel packages (even though not all of them are compatible with October, at least not without some hacks). Concerning themes, the low number of offerings implies we will most likely need to develop our own theme by hiring a developer for the task. In fact, I dare say that the theme in October CMS will most likely be a custom development, since themes and plugins are not thoroughly decoupled (as explained earlier), with the consequence that a market for easily-swappable themes is more difficult to arise. (This is a temporary problem though: once this pull request is resolved, pages will be able to be stored in the database, and swapping themes should not disrupt functionality.)

In my opinion, because of the smaller offerings of themes and plugins, creating a simple site with OctoberCMS will be more expensive than creating a simple WordPress site. For complex sites, however, October’s better architecture (Object-Oriented Programming and Model-View-Controller paradigms) makes the software more maintainable and, as a consequence, potentially cheaper.

Community

Being a part of and having access, WordPress’ community represents one of the most compelling reasons for using WordPress. This is not simply as a matter of size (powering nearly one third of all websites in the world, there are so many stakeholders involved with WordPress, and its community is representatively big) but also as a matter of diversity. The WordPress community involves people from many different professions (developers, marketers, designers, bloggers, sales people, and so on), from all continents and countries, speaking countless languages, from different social, educational and economic backgrounds, with or without disabilities, from corporate, not-for-profit and governmental organizations, and others. Hence, it is quite likely that, for whatever problem we encounter, somebody will be able to help on any of the support forums. And contributing to WordPress is pretty straightforward too: The Make WordPress group congregates stakeholders interested in supporting different projects (accessibility, design, internationalization, and many others) and organizes how and how regularly they communicate — mostly through some dedicated channel on its Slack workspace.

Furthermore, the WordPress community is real and tangible: it doesn’t exist just online, but it gathers offline in WordCamps and meetups all over the world; in 2018, there were a total of 145 WordCamps in 48 countries with over 45,000 tickets sold, and a total of 5,400 meetup events from 687 meetup groups. Hence, it is likely that there is a local chapter nearby which anyone can join to ask for help, learn how to use the platform, keep learning on a regular basis, and teach others as well. In this sense, WordPress is not just a CMS but, more importantly, it’s also people, and considering to leave WordPress should never be done only on its technical merits but on the power of its community, too.

WordCamp Kuala Lumpur 2017 drew more than 200 attendees, coming from several countries. (Large preview)

October CMS’ community is nothing near in size or diversity as WordPress’, even though it has been growing steadily following the increasing popularity of the software. October provides a support forum to ask for help, however, it is not very active. A Slack workspace exists which is pretty active and where, quite importantly, October’s founders participate regularly, helping make sure that all enquiries are properly addressed. This channel is a great source for learning low-level tips and tricks about the software, however, it is geared towards developers mainly: There are no channels concerning accessibility, design, internationalization, and other topics as in the WordPress community, at least not yet. Currently, there are no conferences concerning October CMS, but there is Laracon, the conference for the Laravel community.

Maintainers And Governance

Can we trust that the software will be maintained in the long term, so that if we decide to start a project today, we will not need to migrate to some other platform down the road? How many people are taking care of developing the software? And who is deciding in what direction the software moves towards?

Powering one-third of all sites in the world, WordPress is not short of stakeholders contributing to the software; hence we need not fear that the software will fall into decay. However, WordPress is going through internal deliberations concerning its governance model, with many members of the community expressing that decisions concerning WordPress’s direction are being taken unilaterally by Automattic, the company running WordPress.com. Center stage of this perception was the decision to launch Gutenberg, which many members disagreed with, and which suffered a lack of proper communication by the project leads during its development and release. As a consequence, many community members are questioning the role of “benign dictator”, which has been historically granted to WordPress’ founder and Automattic’s CEO Matt Mullenweg, and researching different governance models to find a more suitable one for the future of WordPress. It is yet to be seen if this quest produces any result, or if the status quo perseveres.

Decisions concerning October CMS’ direction are mainly taken by founders Alexey Bobkov and Samuel Georges and developer and community manager Luke Towers, which keep the project going strong. October CMS doesn’t have the luxury of having a governance problem yet: Its current concern is how to make the project sustainable by generating income for the core software’s maintainers.

Documentation

WordPress documentation in its own site is not extremely comprehensive, but it does the job reasonably well. However, when taking all of the documentation about WordPress into account from all sources, such as general sites (Smashing Magazine, CSS tricks, and many others), specialized sites (WPShout, WPBeginner, and many others), personal blogs, online courses, and so on, there is practically no aspect of dealing with WordPress that hasn’t already been covered.

October CMS doesn’t enjoy anything near the many third-party courses, tutorials or blog posts about it as much as WordPress does, however, the documentation on its site is reasonably comprehensive and certainly enough to start coding. October founders also regularly add new documentation through tutorials. One aspect that I personally enjoyed is the duplication of Laravel’s documentation into October’s documentation for everything of relevance, so the reader must not fill the gaps by him/herself and having to guess what is October’s domain and what is Laravel’s. However, this is not 100% perfect. October’s documentation uses terms originating from Laravel, such as middleware, service containers, facades and contracts, without adequately explaining what these are. Then, reading Laravel’s documentation in advance can be helpful (luckily, Laravel’s documentation is decidedly comprehensive, and Laravel’s screencasts, Laracasts, are another great source of learning, not just concerning Laravel but web development in general).

Conclusion

I set out to discover what features may be enticing for developers looking for alternatives to WordPress by comparing WordPress to a similar CMS, which I defined as being free and open source, based in PHP and producing dynamic content, and enjoying the support from some community. From the CMSs fulfilling these conditions, I chose October CMS for the comparison because of the knowledge I got about it, and because I appreciated its clean and modular coding approach as provided by Laravel, which could offer a fresh and modern perspective for building sites.

This article did not intend to pick a winner, but simply analyze when it makes sense to choose one or the other CMS, highlighting their strengths and weaknesses. There is no “best” CMS: only the most suitable CMS for a specific situation. Furthermore, anyone looking for a CMS to use on a particular project with a specific team and given a certain budget, should do some research and compare all the offerings out there to find out which one is most suitable for the particular context. It’s important not to limit to a few CMSs as I’ve done here in this article, but instead give a chance to all of them.

On a personal note, as a developer, what I found in October CMS is really appealing to me, mostly its ability to build modular applications as provided through Laravel. I would certainly consider this CMS for a new website. However, in the process of writing this article I also “rediscovered” WordPress. Being so popular, WordPress receives more than its fair share of criticisms, mostly concerning its old codebase and, since recently, the introduction of Gutenberg; however, WordPress also has certain excellent features (such as its super-scalable database model) which are seldom praised but should be taken into account too. And most importantly, WordPress should not be considered on its technical aspects alone: in particular, the size of its community and ecosystem places it a level or two above its alternatives. In a nutshell, some projects may benefit from sticking to WordPress, while others may better rely on October CMS or another platform.

As a final note, I would like to remark that exploring how another CMS works is a very rewarding activity on its own, independent of the decision reached concerning whether to use that particular CMS or not. In my case, I had been working for years on WordPress alone, and delving into October CMS was very refreshing since it taught me many things (such as the existence of PHP Standards Recommendations) which I had not been exposed to through WordPress. I may now decide to switch CMSs, or stick to WordPress knowing how to produce better code.

Further Reading on SmashingMag: (rb, ra, yk, il)
Categories: Around The Web

Monthly Web Development Update 3/2019: React Hooks, Constructable Stylesheets, And Building Trust

Smashing Magazine - Fri, 03/15/2019 - 8:13am
Monthly Web Development Update 3/2019: React Hooks, Constructable Stylesheets, And Building Trust Monthly Web Development Update 3/2019: React Hooks, Constructable Stylesheets, And Building Trust Anselm Hannemann 2019-03-15T13:13:08+01:00 2019-03-23T22:35:13+00:00

Do you sometimes feel like there’s so much to read and learn that your brain can’t take it anymore? It’s something most of us experience from time to time when we have too much to do and then overload our brains with even more. I’m aware that my reading lists aren’t helpful in that regard, as they contain even more things to learn. But it’s the very reason why I try to compile a diverse, open-minded set of articles that aren’t entirely frontend- or tech-related. And in weeks like this one where there aren’t too many articles for me to summarize, I realize how relieving this can be. Let’s give our brains the chance to wind down a bit when it tells us to and use the opportunity to reconsider how we do work.

Think about how you approach tasks, for example. Do you ask for more details when you’re given a specific task? Do you figure out how to do it yourself? Or are you just following the task’s details? Only doing the latter will get things done, of course. But it’ll also increase the risk that you forget about necessary details, as a study on storing passwords reveals now. If there’s nothing in the task description about hashing a password, for example, many people will not apply it, even if they know it’s the better solution. Or take the process of building a website: If we forget to add the correct caching, server costs will be unnecessarily high, and performance will suffer. It’s these little extra steps of thinking that make the difference between good, solid work and “just getting stuff done”.

News
  • Chrome 74 brings some new features to DevTools: It now highlights all elements that are affected by a CSS property, Lighthouse 4 is integrated into the Audits panel, and a WebSocket binary message viewer has been added, too.
  • Intersection Observer is still quite new and yet Chrome developers are currently introducing version 2 to tackle some common problems and implement learnings from the first version. Here’s what’s going to change in Intersection Observer v2.
General
  • It’s easy to forget about it, but even today we often build non-diverse solutions in many areas of life. This article shows how that happens with car crash test dummies that neglect women.
  • Voice is becoming more and more important in our lives, mainly because we use more devices without real display interfaces today — Homepod, Alexa, Siri, Google Assistant, or Amazon Echo. Mozilla teamed up with institutes from around the world to create an open-source pool of high-quality voices that helps teach machines how real people speak.
  • “In our modern world, it’s easy to junk things up. Simple is hard. We’re quick to add more questions to research surveys, more buttons to a digital interface, more burdens to people”. Kate Clayton explores how to be an elegant simplifier.
  • “People think that data is in the cloud, but it’s not. It’s in the ocean.” Let’s dive deep into how communication works and how it came to be that Microsoft, Google, Facebook, and Amazon own more than half of the undersea bandwidth. The article shows how the Internet depends on these big four companies nowadays and that we’d face massive struggles and performance impacts if we avoided them.
  • Jason Miller wrote an introduction to rendering on the web, summarizing what happens when a user accesses a website through a modern browser. There’s a lot to learn in here.
Data is not in the cloud. It’s in the ocean. And more than half of the undersea bandwidth is owned by Amazon, Facebook, Google, and Microsoft. (Image credit) UI/UX
  • Anand Satyan explains why it’s important to start designing without color first. It helps you understand the structure of data and layout better and often results in cleaner, more consistent designs.
  • Brad Frost wrote about the importance of providing forms that are simple, not clever, especially if you want users to log in.
  • Nikita Prokopov tried to analyze and redesign Github’s repository page. While I don’t like the final result too much, there are a lot of great takeaways from improving existing design patterns and the user experience with simple methods.
JavaScript CSS
  • Constructable Stylesheets is a new way of initializing an external stylesheet or set of styles in a non-blocking way. This new approach allows us to dynamically construct stylesheets via JavaScript which is especially useful when we use it for Web Components in a ShadowDOM. The feature is available in Chrome Preview builds currently.
  • Rachel Andrew explains how we’re going to break boxes with the new CSS Fragmentation specification. With CSS Fragmentation, we can do things we used to do with float, but it’s more flexible and helps us control page breaks and other things relevant for print or eBooks.
  • This CSS-only experiment is mind-blowing. I’m seriously impressed and wouldn’t have imagined that we can do something like this with CSS today.
Security Web Performance Accessibility
  • Ben Robertson shares five tools we can use for automated accessibility audits. This is great because it allows us to use these tools in CIs, in regression testing (e.g., via Selenium or Chrome/Firefox headless browsers), or directly in our browsers.
  • Alex Carpenter summarized takeaways from WebAIM’s recent accessibility analysis of the top one million sites: 59% of form inputs are unlabeled and, thus, not accessible. Making them accessible for everyone wouldn’t be hard at all. It’s as easy as wrapping the input and describing it, for example like this: <label>Name<input name="name"></label> Of course, there are even better labeling practices out there, but this would already be enough to make a difference for all users of a website, not only those who rely on assistive technologies.
  • Accessibility Insights is a new platform service that provides developers with tools to analyze the accessibility of their web projects.
The Accessibility Insights extension helps you spot accessibility errors and shows how to fix them. (Image credit) Work & Life
  • How do we build trust as leaders? Claire Lew shares why business retreats and team building activities don’t matter much compared to the things that really make a difference: showing vulnerability, communicating the intent behind actions, and, finally, following through on commitments.
  • I found this article by Sahil Lavingia, the founder of Gumroad, very insightful. In it, he shares the failures, the struggles, and the bad decisions when getting Venture Capital, and why having a “normal” company is worth a thought, too, to prevent the whole thing from failing.
  • Our children are technology-focused and spend a lot of time in front of screens, playing games or watching videos. Pamela Paul advocates for letting our children get bored again.
Going Beyond…

Here’s one more thing: The periodic — yet not regular — reminder to give something back if you enjoy reading my writings and summary of articles. — Anselm

(cm)
Categories: Around The Web

Block Kit: Slack’s Contribution To Building A Better Collaboration UI

Smashing Magazine - Thu, 03/14/2019 - 10:30am
Block Kit: Slack’s Contribution To Building A Better Collaboration UI Block Kit: Slack’s Contribution To Building A Better Collaboration UI Suzanne Scacca 2019-03-14T15:30:41+01:00 2019-03-23T22:35:13+00:00

(This is a sponsored article.) Over the last few years, there’s been a major shift in terms of the way companies work. As more businesses become location independent, collaboration tools have become the standard way in which teams meet and get work done.

That said, just because we have collaboration apps that integrate our connected business processes and tools, that doesn’t mean the experience always leads to optimum efficiency or productivity. Why? Well, sometimes an unfriendly UI gets in the way.

That’s why, today, I’m going to talk about Block Kit, Slack’s contribution to building a better collaboration UI.

For those of you who have built a custom Slack app (for the app directory or for internal purposes), this will be your introduction to the new design tool. For those of you who haven’t, that’s okay. There are some valuable lessons to take away from this in terms of what makes for an engaging workspace that will improve collaboration.

Developers, Do You Know What Slack Has Been Working On?

Slack has made huge strides since its launch in 2013. What originally began as a messaging app has now blossomed into a powerful collaboration platform.

As of writing this: Slack has over 10 million active users daily — and they live all over the world (over 150 countries, to be exact).

Here’s an example of a Slack channel for Japanese speakers. (Image source: Slack) (Large preview)

It’s not just individuals using Slack either — nearly 585,000 teams of three persons or more collaborate within the platform. 65 of the Fortune 100 companies also happen to be on Slack.

A glimpse into real-time collaboration between Slack users (Image source: Slack) (Large preview)

This is all thanks to the Slack API which has opened the door for developers to create and publish publicly available apps that extend the functionality of Slack workspaces.

The front page of the Slack App Directory. (Image source: Slack) (Large preview)

This way, Slack users don’t have to bounce between their most commonly used business tools. Related processes may take place all from within Slack.

Sometimes, though, what’s available in the Slack App Directory just isn’t enough for what your organization needs internally. You may be able to bridge some of the divides between your business tools with what’s there, but you might also find a reason to build your own custom Slack apps.

Introducing Block Kit From Slack

Here’s the thing: while Slack has succeeded in allowing developers to craft their own apps to enhance collaboration within the platform, how are developers supposed to know how to create a good experience with it?

Until recently, Slack’s API and app directory provided limited flexibility and control. As Brian Elliott, the General Manager of the Platform, explained:

“Today, all apps are forced into a limited set of ways to display rich information. If you’ve looked at and seen and used all of the different apps in Slack, many of them end up with the same layout regardless of which functionality they’re trying to deploy. When in reality what you need is a set of components that let you build rich interactive displays that are easier for people to comprehend, digest and act on.”

So, Slack developed Block Kit.

Block Kit is a UI framework that enables developers, designers and front-end builders to display their messaging apps through a rich, interactive and intuitive UI. Further, by providing a set of stackable UI elements or blocks, Block Kit now provides developers with more control and flexibility over their app designs and layouts.

Note: If you’d like to see Block Kit in action, join the upcoming Slack session, “Building with Block Kit”, where you’ll get a live product demonstration and see how easy it is to customize the design of your app.

Block Kit comes with two key components:

1. Block Kit Builder

Notice the similarity between this builder tool and many of the other tools we use to create websites and apps for clients:

A demo of the Block Kit builder (Image source: Block Kit) (Large preview)

The building components are on the left. Simply click on the one you want to include and watch it get added to the preview of your app in the center.

Want further customization? Check out the text editor on the right. While Block Kit provides pre-made elements that follow best practices for designing messaging apps, you have the ability to make them your own if you prefer.

2. Block Kit Templates

While you can certainly craft a messaging interface from the Builder on your own, I’d suggest exploring the Templates provided as well:

These are just some of the templates Block Kit offers to users. (Image source: Block Kit) (Large preview)

The Slack Team has already seen really useful cases of Slack apps in action. Needless to say, they know what kinds of things your organization might want to leverage for the sake of improved collaboration.

That’s why you’ll find common actions like the following already built out for you:

  • Reviewing requests for approval;
  • Taking action on new notifications;
  • Running polls and monitoring results;
  • Conducting a search.

Guru is one such tool that’s leveraged Block Kit to improve its Slack app:

Guru provides a database search function within Slack. Results are now quickly retrieved and more clearly displayed on the frontend of Slack.

The Keys To Building A Better Collaboration UI

Now that we’ve seen what’s coming down the line with Block Kit, we need to talk about how it’s going to help you create apps that lead to more productive collaboration.

Blocks

I recently spoke on the subject of Gutenberg and how designers can use it to their advantage. Although the new WordPress editor clearly has its flaws, there’s no questioning why the team at WordPress made the change:

Block builders are the future of web design.

I get that block builders tend to be the preferred tool for web designers and DIY users. Builders allow for visual frontend design and often include abundant customization options.

Some of the pre-made blocks included in Block Kit (Image source: Block Kit) (Large preview)

But Block Kit does much more than that, which means both designers and developers can build custom apps with ease.

Code

The key differentiator between something like a website builder and the Block Kit builder is the coding aspect.

In most cases, designers use page builders so they don’t have to bother with code. They might add some custom CSS classes or add HTML to their text, but that’s generally it. Developers don’t work like that though.

Block Kit includes a panel with pre-written JSON that developers can copy-and-paste into their own Slack app once it’s ready to go. Rather than leave developers to write their own code, Slack provides code that utilizes best practices for speed and design.

A sample of JSON you get when you build your rich messaging experience in the builder. (Image source: Block Kit) (Large preview)

This enables developers to focus on making customizations instead of on having to build their apps from the ground up.

Consistency

When Slack users step inside the platform, they know what to expect. Every interface is the same from workspace to workspace.

However, when an API allows developers to build apps to integrate with those spaces, there’s a risk of introducing elements that just don’t jibe well. When that happens, the unpredictability of the interface can create confusion and hesitation for the end user. Ill-fitting layout choices can also harm the experience.

Block Kit enables developers to build apps with stackable UI components that have been tried and tested. When customizing an experience within an already established platform, it can be difficult to know how far you can take it — or if it’ll even work. Slack has taken those questions out of the equation.

Spacing

This is what the traditional Slack exchange looks like:

An example of Slack users messaging one another (Image source: Slack) (Large preview)

It tends to be a single-column, back-and-forth exchange. And this works perfectly well for Slack channels where collaboration is simple. Employees message about the status of a task. A client uploads a missing asset. The CEO shares a link to a press release mentioning the company. But not all workspaces are that simple.

Block Kit helps you maximize and enhance the space that your app’s features occupy. For instance, Block Kit enables companies like Optimizely to display pertinent information in two-column formats for better readability.

Optimizely uses Block Kit to create two-column formats. (Image source: Optimizely) (Large preview)

This is indeed a better way to share pertinent details in your team’s Slack app.

Rich Interactions

Another way to elevate your app is by turning the integration into one that’s rich with interactions.

Blocks have been specially developed to enhance the most commonly used elements in Slack collaboration. For example:

  • Use the Sectional block for better organization.
  • Use the Text block to customize how messages display.
  • Use properly sized Image blocks so you can stop worrying about whether or not they’ll display correctly.
  • Use Context blocks to show bylines or additional context about messages (like author, comments, changes, etc.)
  • Use Divider blocks to improve the look of the app.
  • Use Action blocks like menu selection, button selection and calendar dates to bring better features into your app and make them display more intuitively.
  • Use 2-section blocks for cleaner layouts.

Doodle has a beautiful example of what can be done with rich interactions using Block Kit:

As you can see, users can work together to schedule meetings just as effectively as if they were using a third-party calendar. The only difference is that they can now do all of that within their Slack workspace.

Wrapping Up

Collaboration is an essential part of any organization’s success, and it doesn’t matter if it’s a team of 3 or a team of 300. But there’s a big difference between working together and productively collaborating.

Thanks to Slack’s API, developers have created some awesome ways to integrate related processes and tools into the platform. And thanks to Block Kit, those external contributions won’t disrupt the experience if the design of the elements falls short.

With intuitive block-building capabilities, developer-friendly coding options and more, Block Kit is going to help developers bring richer experiences and better collaboration to the Slack platform.

One last thing to mention:

Slack’s Frontiers conference is coming up soon. It’s going to be in San Francisco on April 24 and 25. If you’re planning to attend, note that the Developers track will include a full day’s training on Block Kit, including workshops, new feature demos, tutorials, as well as one-on-one mentoring. If you’re thinking about Block Kit, this is an opportunity you won’t want to miss.

(ms, ra, il)
Categories: Around The Web

How To Build An Endless Runner Game In Virtual Reality (Part 2)

Smashing Magazine - Wed, 03/13/2019 - 7:00am
How To Build An Endless Runner Game In Virtual Reality (Part 2) How To Build An Endless Runner Game In Virtual Reality (Part 2) Alvin Wan 2019-03-13T12:00:35+01:00 2019-03-23T22:35:13+00:00

In Part 1 of this series, we’ve seen how a virtual reality model with lighting and animation effects can be created. In this part, we will implement the game’s core logic and utilize more advanced A-Frame environment manipulations to build the “game” part of this application. By the end, you will have a functioning virtual reality game with a real challenge.

This tutorial involves a number of steps, including (but not limited to) collision detection and more A-Frame concepts such as mixins.

Prerequisites

Just like in the previous tutorial, you will need the following:

  • Internet access (specifically to glitch.com);
  • A Glitch project completed from part 1. (You can continue from the finished product by navigating to https://glitch.com/edit/#!/ergo-1 and clicking “Remix to edit”;
  • A virtual reality headset (optional, recommended). (I use Google Cardboard, which is offered at $15 a piece.)
Step 1: Designing The Obstacles

In this step, you design the trees that we will use as obstacles. Then, you will add a simple animation that moves the trees towards the player, like the following:

Template trees moving towards player (Large preview)

These trees will serve as templates for obstacles you generate during the game. For the final part of this step, we will then remove these “template trees”.

To start, add a number of different A-Frame mixins. Mixins are commonly-used sets of component properties. In our case, all of our trees will have the same color, height, width, depth etc. In other words, all your trees will look the same and therefore will use a few shared mixins.

Note: In our tutorial, your only assets will be mixins. Visit the A-Frame Mixins page to learn more.

In your editor, navigate to index.html. Right after your sky and before your lights, add a new A-Frame entity to hold your assets:

<a-sky...></a-sky> <!-- Mixins --> <a-assets> </a-assets> <!-- Lights --> ...

In your new a-assets entity, start by adding a mixin for your foliage. This mixins defines common properties for the foliage of the template tree. In short, it is a white, flat-shaded pyramid, for a low poly effect.

<a-assets> <a-mixin id="foliage" geometry=" primitive: cone; segments-height: 1; segments-radial:4; radius-bottom:0.3;" material="color:white;flat-shading: true;"></a-mixin> </a-assets>

Just below your foliage mixin, add a mixin for the trunk. This trunk will be a small, white rectangular prism.

<a-assets> ... <a-mixin id="trunk" geometry=" primitive: box; height:0.5; width:0.1; depth:0.1;" material="color:white;"></a-mixin> </a-assets>

Next, add the template tree objects that will use these mixins. Still in index.html, scroll down to the platforms section. Right before the player section, add a new tree section, with three empty tree entities:

<a-entity id="tree-container" ...> <!-- Trees --> <a-entity id="template-tree-center"></a-entity> <a-entity id="template-tree-left"></a-entity> <a-entity id="template-tree-right"></a-entity> <!-- Player --> ...

Next, reposition, rescale, and add shadows to the tree entities.

<!-- Trees --> <a-entity id="template-tree-center" shadow scale="0.3 0.3 0.3" position="0 0.6 0"></a-entity> <a-entity id="template-tree-left" shadow scale="0.3 0.3 0.3" position="0 0.6 0"></a-entity> <a-entity id="template-tree-right" shadow scale="0.3 0.3 0.3" position="0 0.6 0"></a-entity>

Now, populate the tree entities with a trunk and foliage, using the mixins we defined previously.

<!-- Trees --> <a-entity id="template-tree-center" ...> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> </a-entity> <a-entity id="template-tree-left" ...> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> </a-entity> <a-entity id="template-tree-right" ...> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> </a-entity>

Navigate to your preview, and you should now see the following template trees.

Template trees for obstacles (Large preview)

Now, animate the trees from a distant location on the platform towards the user. As before, use the a-animation tag:

<!-- Trees --> <a-entity id="template-tree-center" ...> ... <a-animation attribute="position" ease="linear" from="0 0.6 -7" to="0 0.6 1.5" dur="5000"></a-animation> </a-entity> <a-entity id="template-tree-left" ...> ... <a-animation attribute="position" ease="linear" from="-0.5 0.55 -7" to="-0.5 0.55 1.5" dur="5000"></a-animation> </a-entity> <a-entity id="template-tree-right" ...> ... <a-animation attribute="position" ease="linear" from="0.5 0.55 -7" to="0.5 0.55 1.5" dur="5000"></a-animation> </a-entity>

Ensure that your code matches the following.

<a-entity id="tree-container"...> <!-- Trees --> <a-entity id="template-tree-center" shadow scale="0.3 0.3 0.3" position="0 0.6 0"> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> <a-animation attribute="position" ease="linear" from="0 0.6 -7" to="0 0.6 1.5" dur="5000"></a-animation> </a-entity> <a-entity id="template-tree-left" shadow scale="0.3 0.3 0.3" position="-0.5 0.55 0"> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> <a-animation attribute="position" ease="linear" from="-0.5 0.55 -7" to="-0.5 0.55 1.5" dur="5000"></a-animation> </a-entity> <a-entity id="template-tree-right" shadow scale="0.3 0.3 0.3" position="0.5 0.55 0"> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> <a-animation attribute="position" ease="linear" from="0.5 0.55 -7" to="0.5 0.55 1.5" dur="5000"></a-animation> </a-entity> <!-- Player --> ...

Navigate to your preview, and you will now see the trees moving towards you.

Template trees moving towards playerTemplate trees moving towards player (Large preview)

Navigate back to your editor. This time, select assets/ergo.js. In the game section, setup trees after the window has loaded.

/******** * GAME * ********/ ... window.onload = function() { setupTrees(); }

Underneath the controls but before the Game section, add a new TREES section. In this section, define a new setupTrees function.

/************ * CONTROLS * ************/ ... /********* * TREES * *********/ function setupTrees() { } /******** * GAME * ********/ ...

In the new setupTrees function, obtain references to the template tree DOM objects, and make the references available globally.

/********* * TREES * *********/ var templateTreeLeft; var templateTreeCenter; var templateTreeRight; function setupTrees() { templateTreeLeft = document.getElementById('template-tree-left'); templateTreeCenter = document.getElementById('template-tree-center'); templateTreeRight = document.getElementById('template-tree-right'); }

Next, define a new removeTree utility. With this utility, you can then remove the template trees from the scene. Underneath the setupTrees function, define your new utility.

function setupTrees() { ... } function removeTree(tree) { tree.parentNode.removeChild(tree); }

Back in setupTrees, use the new utility to remove the template trees.

function setupTrees() { ... removeTree(templateTreeLeft); removeTree(templateTreeRight); removeTree(templateTreeCenter); }

Ensure that your tree and game sections match the following:

/********* * TREES * *********/ var templateTreeLeft; var templateTreeCenter; var templateTreeRight; function setupTrees() { templateTreeLeft = document.getElementById('template-tree-left'); templateTreeCenter = document.getElementById('template-tree-center'); templateTreeRight = document.getElementById('template-tree-right'); removeTree(templateTreeLeft); removeTree(templateTreeRight); removeTree(templateTreeCenter); } function removeTree(tree) { tree.parentNode.removeChild(tree); } /******** * GAME * ********/ setupControls(); // TODO: AFRAME.registerComponent has to occur before window.onload? window.onload = function() { setupTrees(); }

Re-open your preview, and your trees should now be absent. The preview should match our game at the start of this tutorial.

Part 1 finished product (Large preview)

This concludes the template tree design.

In this step, we covered and used A-Frame mixins, which allow us to simplify code by defining common properties. Furthermore, we leveraged A-Frame integration with the DOM to remove objects from the A-Frame VR scene.

In the next step, we will spawn multiple obstacles and design a simple algorithm to distribute trees among different lanes.

Step 2 : Spawning Obstacles

In an endless runner game, our goal is to avoid obstacles flying towards us. In this particular implementation of the game, we use three lanes as is most common.

Unlike most endless runner games, this game will only support movement left and right. This imposes a constraint on our algorithm for spawning obstacles: we can’t have three obstacles in all three lanes, at the same time, flying towards us. If that occurs, the player would have zero chance of survival. As a result, our spawning algorithm needs to accommodate this constraint.

In this step, all of our code edits will be made in assets/ergo.js. The HTML file will remain the same. Navigate to the TREES section of assets/ergo.js.

To start, we will add utilities to spawn trees. Every tree will need a unique ID, which we will naively define to be the number of trees that exist when the tree is spawned. Start by tracking the number of trees in a global variable.

/********* * TREES * *********/ ... var numberOfTrees = 0; function setupTrees() { ...

Next, we will initialize a reference to the tree container DOM element, which our spawn function will add trees to. Still in the TREES section, add a global variable and then make the reference.

... var treeContainer; var numberOfTrees ... function setupTrees() { ... templateTreeRight = ... treeContainer = document.getElementById('tree-container'); removeTree(...); ... }

Using both the number of trees and the tree container, write a new function that spawns trees.

function removeTree(tree) { ... } function addTree(el) { numberOfTrees += 1; el.id = 'tree-' + numberOfTrees; treeContainer.appendChild(el); } ...

For ease-of-use later on, you will create a second function that adds the correct tree to the correct lane. To start, define a new templates array in the TREES section.

var templates; var treeContainer; ... function setupTrees() { ... templates = [templateTreeLeft, templateTreeCenter, templateTreeRight]; removeTree(...); ... }

Using this templates array, add a utility that spawns trees in a specific lane, given an ID representing left, middle, or right.

function function addTree(el) { ... } function addTreeTo(position_index) { var template = templates[position_index]; addTree(template.cloneNode(true)); }

Navigate to your preview, and open your developer console. In your developer console, invoke the global addTreeTo function.

> addTreeTo(0); # spawns tree in left lane Invoke addTreeTo manually (Large preview)

Now, you will write an algorithm that spawns trees randomly:

  1. Pick a lane randomly (that hasn’t been picked yet, for this timestep);
  2. Spawn a tree with some probability;
  3. If the maximum number of trees has been spawned for this timestep, stop. Otherwise, repeat step 1.

To effect this algorithm, we will instead shuffle the list of templates and process one at a time. Start by defining a new function, addTreesRandomly that accepts a number of different keyword arguments.

function addTreeTo(position_index) { ... } /** * Add any number of trees across different lanes, randomly. **/ function addTreesRandomly( { probTreeLeft = 0.5, probTreeCenter = 0.5, probTreeRight = 0.5, maxNumberTrees = 2 } = {}) { }

In your new addTreesRandomly function, define a list of template trees, and shuffle the list.

function addTreesRandomly( ... ) { var trees = [ {probability: probTreeLeft, position_index: 0}, {probability: probTreeCenter, position_index: 1}, {probability: probTreeRight, position_index: 2}, ] shuffle(trees); }

Scroll down to the bottom of the file, and create a new utilities section, along with a new shuffle utility. This utility will shuffle an array in place.

/******** * GAME * ********/ ... /************* * UTILITIES * *************/ /** * Shuffles array in place. * @param {Array} a items An array containing the items. */ function shuffle(a) { var j, x, i; for (i = a.length - 1; i > 0; i--) { j = Math.floor(Math.random() * (i + 1)); x = a[i]; a[i] = a[j]; a[j] = x; } return a; }

Navigate back to the addTreesRandomly function in your Trees section. Add a new variable numberOfTreesAdded and iterate through the list of trees defined above.

function addTreesRandomly( ... ) { ... var numberOfTreesAdded = 0; trees.forEach(function (tree) { }); }

In the iteration over trees, spawn a tree only with some probability and only if the number of trees added does not exceed 2. Update the for loop as follows.

function addTreesRandomly( ... ) { ... trees.forEach(function (tree) { if (Math.random() < tree.probability && numberOfTreesAdded < maxNumberTrees) { addTreeTo(tree.position_index); numberOfTreesAdded += 1; } }); }

To conclude the function, return the number of trees added.

function addTreesRandomly( ... ) { ... return numberOfTreesAdded; }

Double check that your addTreesRandomly function matches the following.

/** * Add any number of trees across different lanes, randomly. **/ function addTreesRandomly( { probTreeLeft = 0.5, probTreeCenter = 0.5, probTreeRight = 0.5, maxNumberTrees = 2 } = {}) { var trees = [ {probability: probTreeLeft, position_index: 0}, {probability: probTreeCenter, position_index: 1}, {probability: probTreeRight, position_index: 2}, ] shuffle(trees); var numberOfTreesAdded = 0; trees.forEach(function (tree) { if (Math.random() < tree.probability && numberOfTreesAdded < maxNumberTrees) { addTreeTo(tree.position_index); numberOfTreesAdded += 1; } }); return numberOfTreesAdded; }

Finally, to spawn trees automatically, setup a timer that runs triggers tree-spawning at regular intervals. Define the timer globally, and add a new teardown function for this timer.

/********* * TREES * *********/ ... var treeTimer; function setupTrees() { ... } function teardownTrees() { clearInterval(treeTimer); }

Next, define a new function that initializes the timer and saves the timer in the previously-defined global variable. The below timer is run every half a second.

function addTreesRandomlyLoop({intervalLength = 500} = {}) { treeTimer = setInterval(addTreesRandomly, intervalLength); }

Finally, start the timer after the window has loaded, from the Game section.

/******** * GAME * ********/ ... window.onload = function() { ... addTreesRandomlyLoop(); }

Navigate to your preview, and you’ll see trees spawning at random. Note that there are never three trees at once.

Tree randomly spawning (Large preview)

This concludes the obstacles step. We’ve successfully taken a number of template trees and generated an infinite number of obstacles from the templates. Our spawning algorithm also respects natural constraints in the game to make it playable.

In the next step, let’s add collision testing.

Step 3: Collision Testing

In this section, we’ll implement the collision tests between the obstacles and the player. These collision tests are simpler than collision tests in most other games; however, the player only moves along the x-axis, so whenever a tree crosses the x-axis, check if the tree’s lane is the same as the player’s lane. We will implement this simple check for this game.

Navigate to index.html, down to the TREES section. Here, we will add lane information to each of the trees. For each of the trees, add data-tree-position-index=, as follows. Additionally add class="tree", so that we can easily select all trees down the line:

<a-entity data-tree-position-index="1" class="tree" id="template-tree-center" ...> </a-entity> <a-entity data-tree-position-index="0" class="tree" id="template-tree-left" ...> </a-entity> <a-entity data-tree-position-index="2" class="tree" id="template-tree-right" ...> </a-entity>

Navigate to assets/ergo.js and invoke a new setupCollisions function in the GAME section. Additionally, define a new isGameRunning global variable that denotes whether or not an existing game is already running.

/******** * GAME * ********/ var isGameRunning = false; setupControls(); setupCollision(); window.onload = function() { ...

Define a new COLLISIONS section right after the TREES section but before the Game section. In this section, define the setupCollisions function.

/********* * TREES * *********/ ... /************** * COLLISIONS * **************/ const POSITION_Z_OUT_OF_SIGHT = 1; const POSITION_Z_LINE_START = 0.6; const POSITION_Z_LINE_END = 0.7; function setupCollision() { } /******** * GAME * ********/

As before, we will register an AFRAME component and use the tick event listener to run code at every timestep. In this case, we will register a component with player and run checks against all trees in that listener:

function setupCollisions() { AFRAME.registerComponent('player', { tick: function() { document.querySelectorAll('.tree').forEach(function(tree) { } } } }

In the for loop, start by obtaining the tree’s relevant information:

document.querySelectorAll('.tree').forEach(function(tree) { position = tree.getAttribute('position'); tree_position_index = tree.getAttribute('data-tree-position-index'); tree_id = tree.getAttribute('id'); }

Next, still within the for loop, remove the tree if it is out of sight, right after extracting the tree’s properties:

document.querySelectorAll('.tree').forEach(function(tree) { ... if (position.z > POSITION_Z_OUT_OF_SIGHT) { removeTree(tree); } }

Next, if there is no game running, do not check if there is a collision.

document.querySelectorAll('.tree').forEach(function(tree) { if (!isGameRunning) return; }

Finally (still in the for loop), check if the tree shares the same position at the same time with the player. If so, call a yet-to-be-defined gameOver function:

document.querySelectorAll('.tree').forEach(function(tree) { ... if (POSITION_Z_LINE_START < position.z && position.z < POSITION_Z_LINE_END && tree_position_index == player_position_index) { gameOver(); } }

Check that your setupCollisions function matches the following:

function setupCollisions() { AFRAME.registerComponent('player', { tick: function() { document.querySelectorAll('.tree').forEach(function(tree) { position = tree.getAttribute('position'); tree_position_index = tree.getAttribute('data-tree-position-index'); tree_id = tree.getAttribute('id'); if (position.z > POSITION_Z_OUT_OF_SIGHT) { removeTree(tree); } if (!isGameRunning) return; if (POSITION_Z_LINE_START < position.z && position.z < POSITION_Z_LINE_END && tree_position_index == player_position_index) { gameOver(); } }) } }) }

This concludes the collision setup. Now, we will add a few niceties to abstract away the startGame and gameOver sequences. Navigate to the GAME section. Update the window.onload block to match the following, replacing addTreesRandomlyLoop with a yet-to-be-defined startGame function.

window.onload = function() { setupTrees(); startGame(); }

Beneath the setup function invocations, create a new startGame function. This function will initialize the isGameRunning variable accordingly, and prevent redundant calls.

window.onload = function() { ... } function startGame() { if (isGameRunning) return; isGameRunning = true; addTreesRandomlyLoop(); }

Finally, define gameOver, which will alert a “Game Over!” message for now.

function startGame() { ... } function gameOver() { isGameRunning = false; alert('Game Over!'); teardownTrees(); }

This concludes the collision testing section of the endless runner game.

In this step, we again used A-Frame components and a number of other utilities that we added previously. We additionally re-organized and properly abstracted the game functions; we will subsequently augment these game functions to achieve a more complete game experience.

Conclusion

In part 1, we added VR-headset-friendly controls: Look left to move left, and right to move right. In this second part of the series, I’ve shown you how easy it can be to build a basic, functioning virtual reality game. We added game logic, so that the endless runner matches your expectations: run forever and have an endless series of dangerous obstacles fly at the player. Thus far, you have built a functioning game with keyboard-less support for virtual reality headsets.

Here are additional resources for different VR controls and headsets:

In the next part, we will add a few finishing touches and synchronize game states, which move us one step closer to multiplayer games.

(rb, ra, yk, il)
Categories: Around The Web

Exploring The Latest Web Design Trends Together With Be Theme

Smashing Magazine - Tue, 03/12/2019 - 10:30am
Exploring The Latest Web Design Trends Together With Be Theme Exploring The Latest Web Design Trends Together With Be Theme Nick Babich 2019-03-12T15:30:16+01:00 2019-03-23T22:35:13+00:00

(This is a sponsored article.) Designers have a strange relationship with trends. On the one hand, when designers follow a crowd, they might feel that they aren’t able to express enough creativity. On the other hand, trends can tell designers a lot about user preferences — what people love, what they hate — and ultimately help designers to create products with better adoption rates.

People are visual creatures, and visual design has a significant impact on the way we understand products. In this article, I want to focus on the most crucial web design trends and illustrate each trend using Be Theme, a responsive multipurpose WordPress theme.

Let’s get started.

1. Digital Illustrations

Digital illustrations have become one of the most important trends in visual design. Relevant illustrations can make your design stand out from a crowd and establish a truly emotional connection with visitors. Illustrations are quite a versatile tool; product designers can use digital illustrations for various purposes: for hero sections, for feature descriptions, or even as a subtle icon in the navigation bar.

Two types of illustrations are popular among digital designers: hand-drawn flat illustrations and three-dimensional ones. Flat hand-drawn ones give an impression of fine craftsmanship, of a hand-made design; it’s relatively easy to see the personal style of the illustrator through their work. Slack, Intercom and Dropbox are just a few companies that use flat hand-drawn illustrations.

Hand-drawn illustrations look and feel personal for users. (Image source: themes.muffingroup) (Large preview)

Three-dimensional illustrations are quite a new trend. Designers started using them to add more realism, blurring the boundary between the digital and physical worlds.

3D illustrations give users the impression that they can almost reach out and touch objects in the scene. (Image source: themes.muffingroup) (Large preview) 2. Vibrant Colors

There is a reason why so many digital product designers strive to use vibrant colors: Vibrant colors give visual interest to a layout. User attention is a precious resource, and one of the most effective ways to grab attention is by using colors that stand out. Bright colors used for the background can capture the visitor’s attention and contribute to a truly memorable experience.

Vivid colors are an excellent way to grab the visitor’s attention. (Image source: themes.muffingroup) (Large preview) 3. Hero Video Headers

“Show, don’t tell” is a foundational principle of good product design. Imagery plays a key role in visual design because it helps the designers to deliver the main idea quickly.

For a long time, web designers have had to use static imagery to convey their main idea. But the situation has changed. High-speed connections make it much easier for web designers to turn their home pages into immersive movie-style experiences. Video engages users, and users are more willing to spend time watching clips. Video clips used in a hero section can vary from a few seconds of looped video to full-length preview clips with audio.

Designers use video to tell stories. (Image source: themes.muffingroup) (Large preview) 4. Split Screen

Split screen is a relatively simple design technique. All you need to do to create one is divide the screen into two parts (usually 50/50) and use each part to deliver a distinct message. This technique translates well on mobile; two horizontal panels of content can be collapsed into vertical content blocks on small screens. The technique works well when you need to deliver two separate messages, as shown below.

Split screen is an excellent choice for e-commerce websites that offer products for both women and men. (Image source: themes.muffingroup) (Large preview)

It also works well when you have to pair a text message with relevant imagery:

Split screen can be used to connect a text message with relevant imagery. (Image source: themes.muffingroup) (Large preview) 5. Geometric Patterns

Designers can use geometric shapes and patterns endlessly to create beautiful ornaments. This technique works equally well for digital products. Designers can use SVG images and high-resolution PNGs with geometric patterns as backgrounds. Such backgrounds scale well, so you won’t have to worry about how they will look on small and large displays.

With geometric patterns, you can let your creativity run wild. (Image source: themes.muffingroup) (Large preview) 6. Gradients and Duotones

Gradients are the multipurpose tool that works in pretty much any type of design. Designers often use gradients to give their work a little more depth. Modern graphic design trends dictate the use of big, bold and colorful gradients, which help designers make a statement.

When it comes to gradients, designers have a lot of creative freedom. They can experiment with various colors and types, using radial gradient, linear gradients, etc. For example, this is what happens when you put a linear one-color gradient overlay on a photo:

One-color gradient overlay on a photo (Image source: themes.muffingroup) (Large preview)

And this is how a radial two-color gradient looks on a plain background:

Two-color gradient over a plain background. (Image source: themes.muffingroup) (Large preview)

The duotone effect was made popular by Spotify, the online music-streaming service. The service was searching for a bold identity for its brand and decided to use duotones in its design.

In the simplest terms, duotones are filters that replace the whites and blacks in a photo with two colors. Duotones can make almost any image match your company’s branding; simply use your brand’s primary color as the duotone filter.

A duotone in the hero image (Image source: themes.muffingroup) (Large preview) 7. Bold Typography

Most designers know that content should always come first in the design process. A design should honor the message that the product’s creators want to deliver to their users. Bold typography helps designers to achieve that. Massive, screen-dominating text puts the written content center stage.

Bold fonts serve a functional purpose — they make it easy to read the text. Consider the following example. This template is an excellent example of how powerful a bold font can be:

Designers can use bold typography to make text the focal point in a graphic. (Image source: themes.muffingroup) (Large preview) Conclusion

“Should I follow the trends?” As a designer, you have to answer that for yourself. But if you want to see how each trend works for your project, you can do it right now. All of the Be Theme examples listed above can serve as excellent starting points for your creative journey.

(ms, ra, yk, il)
Categories: Around The Web

Creating A Spotify-Powered App Using Nuxt.js

Smashing Magazine - Tue, 03/12/2019 - 7:15am
Creating A Spotify-Powered App Using Nuxt.js Creating A Spotify-Powered App Using Nuxt.js Cher Scarlett 2019-03-12T12:15:39+01:00 2019-03-23T22:35:13+00:00

We’ve all heard of Spotify. Launched back in 2008, the app offers millions of tracks from various legendary and upcoming artists. It allows you to create a playlist, follow other people or choose a playlist based on your mood.

But let’s take the app from another perspective today. Let’s build a two-page server-side rendered web application featuring a “Now Playing on Spotify” component. I’ll walk you through all of the steps of building a client-side application, building and connecting to a server API, as well as connecting to external API services.

Our project will be built using the Node.js and npm ecosystems, Github to store our code, Heroku as our host, Heroku’s Redis for our storage, and Spotify’s web API. The application and internal API will be build entirely using Nuxt’s system. Nuxt is a server-side-rendering framework that runs on Vuejs, Expressjs, Webpack, and Babeljs.

This tutorial is moderately complex, but is broken down into very consumable sections. You’ll find a working demo at cherislistening.heroku.com.

Final result created with Nuxt.js, Redis, and Spotify. (Large preview) Requirements

This tutorial requires knowledge of HTML, CSS, Javascript (ES6), and how to use command line or terminal. We’ll be working with Node.js and Vuejs; a basic understanding of both will be helpful before starting this tutorial. You’ll also need to have Xcode Tools installed if you are on MacOS.

If you prefer to reverse-engineer, you can fork the repository.

Table Of Contents
  1. Planning Our Application
    We’ll lay out our expected functionality and a visual representation of what we plan to see when we are finished.
  2. Setting Up And Creating Our Project
    We’ll walk through how to setup an application hosted on Heroku’s server, setup auto-deployment from Github, setup Nuxt using the command line tools, and get our local server running.
  3. Building Our API Layer
    We’ll learn how to add an API layer to our Nuxt application, how to connect to Redis, and Spotify’s web API.
  4. Client-Side Storage And State Management
    We’ll look at how we can leverage the built-in Vuex store to keep what’s playing up to date. We’ll set up our initial data connections our API.
  5. Building The Pages And Components
    We’ll take a brief look into how pages and components differ in Nuxt, and build two pages and a couple of components. We’ll use our data to build our Now Playing app and some animations.
  6. Publishing Our Application
    We’ll get our app onto GitHub and built on Heroku’s server, authenticate and share with everyone what music we’re listening to.
Planning Our Application

The most important step before we start any new project is to plan our goals. This will help us establish a set of requirements for accomplishing our goals.

  • How many pages are there?
  • What do we want on our pages?
  • Do we want our Spotify “Now Playing” component present on both of our pages?
  • Do we want a progress bar to show listeners where we are in the song?
  • How do we want our pages laid out?

These are the types of questions that will help us draft our requirements.

Let’s build out two pages for our application. First, we want a landing page with our “Now Playing” component. Our second page will be our authentication area where we connect our data to Spotify. Our design is going to be very minimalistic, to keep things simple.

For our “Now Playing” component, let’s plan on showing the progress of the track as a bar, the name of the track, the artist’s name, and the album art. We’ll also want to show an alternate state showing the most recent track played, in case we aren’t currently listening to anything.

Since we are dealing with Spotify’s API, we’ll have special tokens for accessing the data from our site. For security purposes, we don’t want to expose these tokens on the browser. We also only want our data, so we’ll want to ensure that we are the only user who can login to Spotify.

The first issue we find in planning is that we have to login to Spotify. This is where our Redis cache storage comes in. Spotify’s API will allow to permanently connect your Spotify account to an application with another special token. Redis is a highly performant in-memory data structure server. Since we are dealing with a token, a simple key:value storage system works well. We want it to be fast so we can retrieve it while our application is still loading.

Heroku has its own Redis cache service built in, so by using Heroku for our server, host, and storage, we can manage everything in one place. With the added benefit of auto-deployment, we can do everything from our console with commands in terminal. Heroku will detect our application language from our push, and will build and deploy it without much configuration.

Setting Up And Creating Our Project Install Nodejs

Grab the right package for your OS here: https://nodejs.org/en/download/

$ node --version v10.0.1 Install git

Follow the instructions for your OS here: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git

$ git --version git version 2.14.3 (Apple Git-98) Sign Up For GitHub

Follow the instructions here: https://github.com/join and https://help.github.com/articles/set-up-git/.

Create a repository: https://help.github.com/articles/create-a-repo/

Clone the repository: https://help.github.com/articles/cloning-a-repository/

I named mine “cherislistening”. Here’s what my clone looks like:

$ git clone https://github.com/cherscarlett/cherislistening.git Cloning into `cherislistening`... remote: Counting objects: 4, done. remote: Compressing objects: 100% (4/4), done. remove: Total 4 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (4/4), done. $ cd cherislistening/ Install And Setup Heroku

Sign up for Heroku here: https://signup.heroku.com/

Download and install the Command Line Interface (CLI): https://devcenter.heroku.com/articles/heroku-cli#download-and-install

We’ll need to login and create our app, along with setting up some configuration variables. I named my app “cherislistening”. You can also leave off the -a command and Heroku will give you a randomly generated name. You can always change it later. The url of your app will be http://<APPLICATION_NAME>.herokuapp.com.

Nuxt requires some specific configuration to build and run properly, so we’ll add those now to get them out of the way.

$ heroku –version heroku/7.19.4 darwin-x64 node-v11.3.0

​$ heroku login heroku: Press any key to open up the browser to login or q to exit: Logging in… done Logged in as cher.scarlett@gmail.com

$ heroku create -a cherislistening

​$ heroku config:set CLIENT_URL=http://cherislistening.herokuapp.com API_URL=/ HOST=0.0.0.0 NODE_ENV=production NPM_CONFIG_PRODUCTION=false Setting CLIENT_URL, API_URL, HOST, NODE_ENV, NPM_CONFIG_PRODUCTION and restarting ⬢ cherislistening… done, v1 API_URL: / CLIENT_URL: http://cherislistening.herokuapp.com HOST: 0.0.0.0 NODE_ENV: production NPM_CONFIG_PRODUCTION: false

Go to the Heroku dashboard and click into your newly created app. In the ‘Deploy’ tab, connect to your Github account, select the repository you cloned, and enable auto deploys from the Master branch.

Github selected in the Heroku dashboard (Large preview) Repository selection in Github (Large preview) Automatic deployment setup with Github (Large preview) Create Nuxt App

We’ll use npx to create our Nuxt application. Npm is a great ecosystem for managing Node.js packages, but to run a package, we must install it and add it to our package.json file. That isn’t very useful if we want to execute a single package one time, and installing something isn’t really necessary. This makes npx suitable for executing packages that compose file trees, adding boilerplates, and install the packages you need during execution.

$ npx --version 6.4.1

npx is shipped by default in npm 5.2.0+, so it is highly recommended we upgrade npm instead of globally installing npx. If you just installed a fresh version of node.js, you should have current npm and npx.

The Nuxt.js team has created a scaffolding tool which will give your application the basic structure required to run. Make sure you’re in your new project’s folder before running the command.

$ npx create-nuxt-app npx: installed 407 in 5.865s > Generating Nuxt.js project in /Users/cstewart/Projects/personal/tutorials/cherislistening ? Project name cherislistening ? Project description A Spotify Now Playing App ? Use a custom server framework none ? Choose features to install Prettier, Axios ? Use a custom UI framework none ? Use a custom test framework none ? Choose rendering mode Universal ? Author name Cher Scarlett ? Choose a package manager npm

npm notice created a lockfile as package-lock.json. You should commit this file.

To get started:

npm run dev

To build and start for production:

npm run build npm start

npm notice created a lockfile as package-lock.json. You should commit this file.

To get started:

npm run dev

To build & start for production:

npm run build npm start

Every folder within the scaffolding comes with a README file. This file will give you the basics for how the folder works, and whether or not it’s needed. We will talk about the folders we’ll use as we get to them in the tutorial.

.nuxt/ assets/ |___README.md components/ |___Logo.vue |___README.md layouts/ |___default.vue |___README.md middleware/ |___README.md node_modules/ pages/ |___index.vue |___README.md plugins/ |___README.md static/ |___favicon.co |___README.md store/ |___README.md .gitignore .prettierrc LICENSE nuxt.config.js package-lock.json package.json README.md

We’ll need to make a change to package.json so that when we deploy to Heroku, our build process will run. In “scripts”, we’ll add "heroku-postbuild": "npm run build". Don’t forget to add a comma after the previous line in the object.

"scripts": { "dev": "nuxt", "build": "nuxt build", "start": "nuxt start", "generate": "nuxt generate", "heroku-postbuild": "npm run build" },

package.json

If you run npm run dev, and go to http://localhost:3000 in your browser, you should see the scaffolded app running:

Initial state of Nuxt application after scaffolding (Large preview) Install Redis

Open a new terminal or command line tab and change directories (cd) into your project’s parent folder. Download redis and run make. If you’re on Windows, you’ll need to check out https://github.com/MicrosoftArchive/redis/releases.

$ cd ../ $ wget http://download.redis.io/releases/redis-5.0.3.tar.gz $ tar xzf redis-5.0.3.tar.gz $ cd redis-5.0.3 $ sudo make install cd src && /Library/Developer/CommandLineTools/usr/bin/make install

Hint: It is a good idea to run ‘make test’.

Categories: Around The Web

Designing An Aspect Ratio Unit For CSS

Smashing Magazine - Mon, 03/11/2019 - 10:00am
Designing An Aspect Ratio Unit For CSS Designing An Aspect Ratio Unit For CSS Rachel Andrew 2019-03-11T15:00:41+01:00 2019-03-23T22:35:13+00:00

One of the things that come up again and again in CSS is the fact that there is no way to size things based on their aspect ratio. In particular when working with responsive designs, you often want to be able to set the width to a percentage and have the height correspond to some aspect ratio. This is something that the folks who are responsible for designing CSS (i.e. the CSS Working Group) have recently been discussing and a proposed solution was agreed upon at a recent CSSWG meeting in San Francisco.

This is a new resolution, so we have no browser implementations yet, but I thought it would be worth writing up the proposal in case anyone in the wider web community could see a showstopping issue with it. It also gives something of an insight into the work of the CSSWG and how issues like this are discussed, and new features designed.

What Is The Problem We Are Trying To Solve?

The issue in in regard to non-replaced elements, which do not have an intrinsic aspect ratio. Replaced elements are things like images or a video placed with the <video> element. They are different to other boxes in CSS as they have set dimensions, and their own behavior. These replaced elements are said to have an intrinsic aspect ratio, due to them having dimensions.

A div or some other HTML element which may contain your content has no aspect ratio, you have to give it a width and a height. There is no way to say that you want to maintain a 16 / 9 aspect ratio, and that whatever the width is, the height should be worked out using the given aspect ratio.

A very common situation is when you want to embed an iframe in order to display a video from a video sharing site such as YouTube. If you use the <video> element then the video has an aspect ratio, just like an image. This isn’t the case if the video is elsewhere and you are using an embed. What you want is for your video to be responsive, yet remain at the correct aspect ratio for the video. What you get however, if you set width to 100%, is the need to then set a height. Your video ends up stretched or squished.

Let’s also look at a really simple case of creating a grid layout with square cells. If we were using fixed column track sizes, then it is easy to get our square cells as we can define rows to be the same size as the column tracks. We could also make our row tracks auto-sized, and have items and set the height on the items.

See the Pen Aspect Ratios Example 1 by Rachel Andrew.

The problem comes when we want to use auto-fill and fill a container with as many column tracks as will fit. We now can’t simply give the items a height, as we don’t know what the width is. Our items are no longer square.

See the Pen Aspect Ratios Example 2 by Rachel Andrew.

Being able to size things based on their aspect ratio would mean we could calculate the correct aspect ratio once the grid item is laid out. Thus making the grid items as tall as they are wide, so that they always maintain as a square no matter what their width.

Current Aspect Ratio Solutions

Web developers have been coping with the lack of any aspect ratio in CSS in various ways — the main one being the “padding hack”. This solution uses the fact that padding % in the block direction (so top and bottom padding in a horizontal top to bottom language) is calculated from the inline size (width).

The article “Aspect Ratio Boxes” on CSS-Tricks has a good rundown of the current methods of creating aspect ratio boxes. The padding hack works in many cases but does require a bunch of hoops to jump through in order to get it working well. It’s also not in the slightest bit intuitive — even if you know why and how it works. It’s those sort of things that we want to try and solve in the CSS Working Group. Personally, I feel that the more we get elegant solutions for layout in CSS, the more the messy hacks stand out as something we should fix.

For the video situation, you can use JavaScript. A very popular solution is to use FitVids — as also described in the CSS-Tricks article. Using JavaScript is a reasonable solution, but there’s more script to load, and also something else to remember to do. You can’t simply plonk a video into your content and it just works.

The Proposed Solution

What we are looking for is a generic solution that will work for regular block layouts (such as a video in an iframe or a div on the page). It should also work if the item is a grid or flex item. There is a different issue of wanting grid tracks to maintain an aspect ratio (having the row tracks match the columns), this solution would fix many cases, however, where you might want that (you would be working from the item out rather than the track in).

The soluion will be part of the CSS Sizing Specification, and is being written up in the CSS Sizing 4 specification. This is the first step for new features being designed by the CSS Working Group, the idea is discussed, and then written up in a specification. An initial proposal for this feature was brought to the group by Jen Simmons, and you can see her slide deck which goes through many of the use cases discussed in this article here.

The new property introduced to the Sizing specification is the aspect-ratio property. This property will accept a value which is an aspect ratio such as 16/9. For example, if you want a square box with the same width and height, you would use the following:

.box { width: 400px; height: auto; aspect-ratio: 1/1; }

For a 16 / 9 box (such as for a video):

.box { width: 100%; height: auto; aspect-ratio: 16/9; }

For the example with the square items in a grid layout, we leave our grid tracks auto-sized, which means they will take their size from the items; we then make our items sized with the aspect-ratio unit.

.grid { display: grid; grid-template-columns: repeat(autofill, minmax(200px, 1fr)); } .item { aspect-ratio: 1/1; }

Features often go through various iterations before browsers start to implement them. Having discussed the need for an aspect ratio unit previously, this time we were looking at one particular concern around the proposal.

What happens if you specify an aspect ratio box, but then add too much content to the box? This same issue is brought up in the CSS-Tricks article about the padding hack — with equally unintuitive solutions required to fix it.

Dealing With Overflow

What we are dealing with here is overflow, as is so often the case on the web. We want to have a nice neatly sized box: our design asks for a nice neatly sized box, our content is less well behaved and turns out to be bigger than we expected and breaks out of the box. In addition to specifying how we ask for an aspect ratio in one dimension, we also have to specify what happens if there is too much content, and how the web developer can tell the browser what to do about that overflowing content.

There is a general design principle in CSS that we use in order to avoid data loss. Data loss in a CSS context is where some of your content vanishes. That might either be because it gets poked off the side of the viewport, or is cropped when it overflows. It’s generally preferable to have a messy overflow (as you will notice it and do something about it). If we cause something to vanish, you may not even realize it, especially if it only happens at one breakpoint.

We have a similar issue in grid layout which is nicely fixed with the minmax() function for track sizes. You can define grid tracks with a fixed height using a length unit. This will give you a lovely neat grid of boxes, however, as soon as someone adds more content than you expected into one of those boxes, you will get overflow.

See the Pen Aspect Ratios Example 3 by Rachel Andrew.

The fix for this in grid layout is to use minmax() for your track size, and make the max value auto. In this case, auto can be thought of as “big enough to fit the content”. What you then get is a set of neat looking boxes that, if more content than expected gets in, grow to accept that content. (Infinitely better than a messy overflow or cropped content.)

In the example below, you can see that while the first row with the box with extra content has grown, the second row is sized at 100 pixels.

See the Pen Aspect Ratios Example 4 by Rachel Andrew.

We need something similar for our aspect ratio boxes, and the suggestion turns out to be relatively straightforward. If you do nothing about overflow, then the default behavior will be that the content is allowed to grow past the height that is inferred by the aspect ratio. This will give you the same behavior as the grid tracks size with minmax(). In the case of height, it will be at least the height defined by the aspect ratio, i.e. if the content is taller, the height can grow to fit it.

If you don’t want that, then you can change the value of overflow as you would normally do. For example hiding the overflow, or allowing it to scroll.

Commenting On Proposals In Progress

I think that this proposal covers the use cases detailed in the CSS-Tricks article and the common things that web developers want to do. It gives you a way to create aspect ratio-sized boxes in various layout contexts, and is robust. It will cope with the real situation of content on the web, where we don’t always know how much content we have or how big it is.

If you spot some real problem with this, or have some other use case that you think can’t be solved, you can directly comment on the proposal by raising an issue in the CSSWG GitHub repo. If you don’t want to do that, you can always comment here, or post to your own blog and link to it here so I can see it. I’d be very happy to share your thoughts with the working group as this feature is discussed.

(il)
Categories: Around The Web

Can You Make More Money With A Mobile App Or A PWA?

Smashing Magazine - Fri, 03/08/2019 - 8:00am
Can You Make More Money With A Mobile App Or A PWA? Can You Make More Money With A Mobile App Or A PWA? Suzanne Scacca 2019-03-08T13:00:16+01:00 2019-03-23T22:35:13+00:00

Let’s be honest. The idea behind building mobile apps, websites or any other branded platforms online is to make money, right? Your clients have contacted you to do this for them in order to maximize their results and, consequently, their profits. If money didn’t matter, they’d use a free website builder tool to throw something — anything — up there and you’d no longer be part of the equation.

Money does matter, and if your clients don’t see a huge return on their investment in an app, it’s going to be quite difficult to sustain a business built around designing apps.

Today, I’m going to talk about why app monetization needs to be one of the first things you think about before making a choice between designing a mobile app or PWA for your clients. And why the smartest thing you can do right now is to steer profit-driven clients to a PWA.

Your Guide To Progressive Web Apps

There are a lot of pain points that users face when browsing old non-PWA websites. Make sure you’re familiar with important technologies that make for cool PWAs, like service workers, web push notifications and IndexedDB. Read more  →

PWA vs. Mobile App Monetization: Consider This

I’ve been watching the MoviePass app closely since it came out. Part of me wanted to hop aboard and start reaping the benefits of the too-good-to-be-true movie app’s promise, but part of me just didn’t see how that kind of business model could be viable or sustainable.

For starters, the subscription service was way underpriced. I realize the makers of the app hoped that many users wouldn’t use their subscriptions to the fullest, or at all, which would then drive profits up on their end. They had a similar suspicion regarding the amount of data they’d be able to mine from users and sell to advertisers and marketers. But, in 2019, that all seems to have been faulty logic with the app in a major downward spiral of profit loss.

It just goes to show you that no matter how popular your mobile app may be, it’s really difficult to make a profit with one. Now, “difficult” does not mean “impossible”. There are certainly mobile apps that make incredible amounts of money. But just because you can make money with a mobile app, does it mean it’s the smartest option for your client? If your client’s end users are craving a convenient, fast and intuitively designed app experience, couldn’t you give them a PWA instead?

If you look at the big picture, you’ll find that there’s a greater opportunity to make money (and, not only that, make a profit) with a PWA when compared to a mobile app.

Let’s explore how we get to that point and how you can use these calculations to determine what the best option is for your client.

  1. The Cost To Build
  2. The Cost To Maintain
  3. The Cost To Acquire Users
  4. The Cost To Monetize
#1: The Cost To Build

Building an app is no easy feat, whether it be a native mobile app or PWA. According to Savvy, there are three tiers of mobile app development options:

Savvy breaks down app building costs into three categories (Source: Savvy) (Large preview)

According to Savvy, small development shops may charge up to $100,000 to build an app. App development agencies can charge up to $500,000. And those targeting enterprises may bill up to $1,000,000.

That said, PWAs aren’t cheap either.

Give Otreva’s “How Much to Build an App” calculator a try. These are the estimated costs I received (top-right corner) to build an e-commerce mobile app that’s feature-rich:

Otreva calculates the cost of building an ecommerce app to be $356k. (Source: Otreva Calculator) (Large preview)

Compare that to the estimated costs to build a progressive web app with the same exact features:

Otreva calculates the cost of building an ecommerce app to be $346k. (Source: Otreva Calculator) (Large preview)

Although the costs here aren’t too far apart, I don’t suspect that to be the case when building less robust apps for clients. As you decrease the amount of features included, you’re likely to find that the gap between the cost of mobile apps and PWAs grows.

Even so, let’s say what you plan to build is comparable in pricing regardless of which app you choose. Keep in mind that these calculators don’t take into consideration the cost of building out the backend server environment (which is something a PWA doesn’t need). Plus, when you compare the timeline of developing a mobile app against a PWA, mobile apps will almost always take longer as you have to build an app for each of the stores you want it to appear in.

So, when you consider the upfront costs of building an app, be sure to look a bit more closely at everything involved. At some point, the revenue you generate is going to have to make up for that investment (i.e. loss).

#2: The Cost To Maintain

Software of any kind must be updated regularly — as does anything you build with it. That’s because designs go stale, security bugs require patches and performance can always be improved. But the way you manage and maintain mobile apps vs. PWAs is incredibly different.

BuildFire has a great roundup of the hidden costs that come with having a mobile app. In it, author Ian Blair shares the most expensive maintenance costs associated with apps:

BuildFire estimates the most expensive mobile app hidden costs. (Source: BuildFire) (Large preview)

Some of these will certainly overlap with PWAs. However, take a look at these three that are specific to mobile apps:

  • App update submissions = $2,400
  • iOS and Android updates = $10,000
  • Servers = $12,000

That’s why you’ll find that most estimates put the cost of annual mobile app maintenance at about 20% of the original upfront cost to build it.

One thing that’s missing from this breakdown is the time-cost of maintaining a mobile app. Because not only are updates costly to manage, but they take a while to happen, too, as app stores have to review any changes to the software or content you’re attempting to push through.

PWAs are significantly easier and cheaper to maintain as they’re web-based applications. So, it’s not all that different from what you would do to keep a website up-to-date.

Yes, the surrounding web hosting architecture, SSL certificate, payment gateways and other integrated technology will require monitoring and maintenance. However, a lot of that stuff is managed by the provider itself. Most of what you have to concern yourself with in terms of maintaining a PWA is the update piece.

When the underlying software has an update available or you simply want to make a change to the content of the PWA, you can push it through to your site (after testing on a staging server first, of course). There’s no app store process you have to follow or to wait for approval from. Changes immediately go live.

Recommended reading: Native And PWA: Choices, Not Challengers!

#3: The Cost To Acquire Users

Once you have a handle on how much the app itself costs, it’s time to wrap your head around the cost of customer acquisition. This is where we’ll start to see PWAs pull far ahead of mobile apps.

For example, here are all the things you have to do in order to acquire users for a mobile app:

Get An App Store Membership

Pay the $99/year Apple Developer Program membership fee or pay the $25 one-time fee to create a Google Play Developer account. You can’t publish to the stores without them.

In-Depth Market Testing

Because a mobile app is such an expensive investment, you can’t afford to throw something into the app store without first doing in-depth audience research and beta testing.

This means looking at the current app market to see if there’s even a need or room for your mobile app. Then, study the target audience and how they’re likely to stumble upon it and convert. Once you have a good hypothesis in place, beta testing will be key to ensure you have a viable strategy in place. (It’ll also be quite expensive, too.)

Decide On A Customer Acquisition Model

Getting someone to install your app from an app store is one thing. Getting users to become an actual customer is another. If you haven’t done so already, figure out what sort of action you’ll require of them before you’re willing to call them a “customer”.

Statista’s 2017–2018 data on the average mobile app user acquisition costs might have you reconsidering your original choice though:

Statista presents estimates for the cost of mobile app customer acquisition. (Source: Statista) (Large preview)

Not only is there a great discrepancy between acquiring a user who’s willing to install your app and someone who’s willing to pay for a subscription, but there’s also a large discrepancy between the cost of converting Android vs. iOS users.

You might find that the monetization model you had hoped to use just won’t pay off in the end. (More on that down below.)

App Store Optimization

Publishing a mobile app to an app store isn’t enough to guarantee users will want to install it. You have to drive traffic to it within each app store.

If you don’t have a tool that’ll help you write descriptions and metadata for the listing, you’ll need to hire a copywriter or agency who can help (and they’re not cheap). Plus, don’t forget about the screenshots of the app in action. There’s still a bit of work to do before you can get that app out to the app stores for review and approval.

Build A Website

Yep, that’s right. Even though your client has spent all this money to build a mobile app, they’re still going to need a website when all is said and done. It’s not going to be a duplicate of the app store though. All they really need is a high-converting landing page that’ll rank in search, bring attention to the app and help drive engaged leads to it.

That said, websites cost money. You’ll need a domain name, web hosting, SSL certificate and perhaps a premium theme or plugin to design it.

Get Good Press

Because you can’t leverage regular ol’ search marketing to drive traffic to your app (since there’s no link to share), you have to rely on online publications and influencers to talk it up on your behalf. You should also be doing this on your own through social media. The only thing is, organic social media marketing takes time.

If you want good press for your mobile app, you’ll have to use paid social ads, search ads and affiliate relationships to help you spread the word quickly.

Retention Rate Optimization

One final customer acquisition cost to factor in is retention rate optimization. As we’ve seen before, all it takes is 30 days for a mobile app to lose up to 90% of its user base. If you’re not continually evaluating the quality of your app and refining it to create a better experience, you might as well double the cost of customer acquisition now.

Consumers, in general, aren’t as eager to spend money with new brands and definitely don’t spend as much as long-time customers do. If you don’t have a plan to develop ways to breed loyalty with current ones, your mobile app is going to bleed a lot of money along the way.

On the other hand, there’s a lot less you must do to acquire users for a progressive web app:

Search Engine Optimization

A PWA is already on the web, so there’s no need to build an additional website to promote it. All you need to worry about now is optimizing it for search. You could do this on your own with free keyword tools and SEO plugins.

However, it’s probably worth investing in an SEO pro or agency if you’re trying to get the app to the top of search ASAP.

Paid Promotions

There’s no need to go to the extent of a mobile app with press pitches, affiliate links or influencer marketing. Instead, you can use paid ads on social media and Google (all within a reasonable budget) to increase the presence of your PWA in search.

Leverage The “Add To Homescreen” Button

Unlike mobile apps which need users to find them within app stores, PWAs are searchable. However, if you’re trying to retain these users and convert them into customers, your best bet is to put the “Add to Homescreen” button in front of them like The Weather Channel does.

The Weather Channel asks visitors to add the PWA to the home screen. (Source: The Weather Channel) (Large preview)

All it takes it one click and they’ll have instant access to your PWA right from their mobile homescreen.

#4: The Cost To Monetize

That doesn’t make sense, does it? The "cost" to monetize? Sadly, it does.

Before I explain the costs, let’s discuss the kinds of monetization that are available for each.

Mobile App Monetization

Paid apps are ones that are completely gated off unless a user pays for subscription access. The New York Times does this, though it gives users a handful of articles to read for free to give them a taste of what they’re missing.

The New York Times app is subscription only. (Source: The New York Times) (Large preview)

Freemium apps are ones that are mostly free, but ask for payment to access certain parts of the app. An example of this is Jackpot Magic Slots, which allows users to create competitive clubs like this one which requires “member” funding:

Jackpot Magic Slots enables users to create clubs that receive funding. (Source: Jackpot Magic Slots) (Large preview)

The catch is that users will inevitably need to purchase coins or spend a lot of time gambling in the app in order to afford those funding fees. So, Jackpot Magic Slots is indirectly making money off of its users.

In-app purchase apps are ones that allow unfettered access to the app. However, they ask for payment from users that want to upgrade their experience with in-app currency, premium features and more. Words with Friends sells Power Ups, Premiums and Coins to customers who want to get more out of their gameplay.

Words with Friends charges for in-app upgrades. (Source: Words with Friends) (Large preview)

Sponsored content apps are ones that publish sponsored ads and content to generate revenue. Facebook, of course, is a master of this seeing as how it’s nearly impossible for businesses to get in front of users otherwise:

Facebook is basically a pay-to-play platform for businesses. (Source: Facebook) (Large preview)

Ad-free apps are ones that accept payment to remove intrusive ads from getting in the way of the app interface.

eCommerce apps are ones that sell goods through their own payment gateways as Fashion Nova does:

Fashion Nova has a mobile app store, too. (Source: Fashion Nova) (Large preview)

Free apps are just what they sound like. However, they aren’t typically available to the public at large. Instead, loyalty users, enterprise customers and others who pay for a premium service in person or online gain access for free.

There’s another way free apps make money and that’s to reward users for referring others to it as is the case with Wordscapes:

Wordscapes rewards users for inviting others to join the app. (Source: Wordscapes) (Large preview)

It might not lead directly to cash in the bank for the app, but it does increase the amount of word-of-mouth referrals which tend to be more valuable in the long run anyway.

The Cost…
As great as all these monetization methods are, there are two big things to note here in terms of what mobile app monetization is going to cost you:

Mobile app stores take a portion of money earned through your app. More specifically, app stores take 30% of your earnings.

This becomes obvious when you compare app store revenues:

Statista tracks mobile app store revenue trends from 2015 to 2020. (Source: Statista) (Large preview)

Against mobile app revenues:

Statista tracks mobile app revenue trends from 2015 to 2020. (Source: Statista) (Large preview)

Note that the app store revenues shown above are about a third of total mobile app revenues. So, your earnings with a mobile app are more like 70% of your projected total earnings.

Another monetization “cost” you have to think about is the fact that app stores don’t pay you out right away.

According to Apple:

Payments are made within 45 days of the last day of the month in which book purchases were made. To receive payment, you must have provided all required banking and tax information and documentation, as well as meeting the minimum payment threshold.

Not only that, but you have to meet a certain minimum threshold. If your app doesn’t generate over a certain limit based on which country you operate out of, you might have to wait longer.

According to Google:

In many cases, Google will initiate a payment on the 15th day of each month or on the next business day, if your bank account has been verified and you've reached a minimum balance, which varies by region.

Google’s minimum threshold is much higher than Apple’s, so you could end up waiting even longer to get paid your app earnings.

In sum, not only are you paying the app stores a membership fee and letting them take a good chunk of your earnings, but you’re paying with your time as well.

PWA Monetization

Subscriptions: Just like mobile apps, PWAs can sell premium access. The Financial Times is an online newspaper that sells premium access to its stories through its PWA:

Financial Times has a PWA that’s subscription-only. (Source: Financial Times) (Large preview)

Freemium access: Since you’re not apt to find a lot of gaming apps as PWAs, freemium access won’t come in the form of things like in-app upgrades. Instead, you’ll see examples like The Billings Gazette which offer subscriptions for a more streamlined news-reading experience:

The Billings Gazette offers survey-free articles for a subscription. (Source: The Billings Gazette) (Large preview)

Advertising: Ads have been a part of the web’s monetization model for a long time now, so it would be odd for PWAs to ignore this obvious choice. Forbes is one such example that uses a lot of advertising on its PWA:

Forbes makes the most of its ad space on its PWA. (Source: Forbes) (Large preview)

Affiliate marketing is another way to collect ad revenue with PWAs.

eCommerce: Traditional ecommerce sales can take place on PWAs, especially since an SSL certificate is required in order to have one. Debenhams is a nice example of a PWA that sells products online through a PWA to generate revenue.

Debenhams attracts mobile shoppers with its ecommerce PWA. (Source: Debenhams) (Large preview)

But that’s not all. Any kind of business can easily convert its website into a PWA and continue selling its products, services, and downloadables. eCommerce monetization is available to everyone.

The Cost…
Compared to how many ways you can earn money with a mobile app, this might seem like a tawdry list. But here’s the thing:

When you make money with a PWA, it’s yours to keep. That is, aside from any affiliate commissions or e-commerce gateway fees you may owe. But neither of those come close to the 30% take the app stores claim.

Additionally, if you’re helping your client make the move from website to PWA (which is much more seamless than website to native app), you can expect a major leap in revenue generation almost right away.

Think with Google interviewed Mobify CEO Igor Faletski to see what sort of monetization trends his company has noticed when it comes to PWAs. Here’s what he said:

Not only can a PWA provide your customers with a richer mobile experience sooner, it can deliver a faster return on investment. And that ROI can potentially offset the cost and risk of the larger digital transformation project.
Our customers typically see a 20% revenue boost with a PWA, so every minute you don’t have a PWA is a minute spent with 20% less revenue on your busiest customer touchpoint. As an example, a retailer with $20 million in annual e-commerce revenue could lose $1.4 million by waiting a month to offer a PWA and another $6.8 million by waiting for six months. Think with Google shows how much money you can earn if you launch a PWA today. (Source: Think with Google) (Large preview)

Want to see a real-life example of how businesses are earning more money by creating a progressive web app? Check out this story about JM Bullion.

Thanks to the major increase in speed with its PWA:

JMBullions.com’s smartphone conversion rate is 28% higher this month compared with the month prior to switching over. Wrapping Up

Before you go rushing out to build a mobile app for your clients, think about the kind of ROI they can realistically expect compared to a PWA.

The upfront costs and ongoing maintenance of a native mobile app are huge. If your client isn’t generating huge sums of money right away (or even before launch), the app itself might not even be a sustainable venture.

However, if you look at the PWA counterpart, not only is it less expensive to build and maintain, but the turnaround times ensure that cash will start to flow in sooner rather than later. Plus, since PWAs are web-based, there’s really no secret to how much work is involved in optimizing them for search or marketing them to the world.

With a shorter learning curve and lower costs, it seems odd to opt for a mobile app when a PWA can provide nearly as good of an experience.

Further Reading on SmashingMag: (ra, yk, il)
Categories: Around The Web

Biometrics And Neuro-Measurements For User Testing

Smashing Magazine - Thu, 03/07/2019 - 7:00am
Biometrics And Neuro-Measurements For User Testing Biometrics And Neuro-Measurements For User Testing Susan Weinschenk 2019-03-07T12:00:20+01:00 2019-03-23T22:35:13+00:00

(This article is sponsored by Adobe.) So it’s time to test the latest version of your app with users. You schedule your first user testing session. The participant enters the room; your lab partner puts velcro on the participant’s finger and fits a headband and head cap on before she sits down at a computer to start the user test session. What’s all this for? It’s biometrics and neuro-measurements.

In a “traditional” user test, you put a participant in front of your app, product, or software and give them tasks to do, ask them to “think aloud”, and observe and record what they say and what they do. You may ask them some questions before and after the session, too. I’ve done thousands of these sessions, and chances are that if you are a user researcher, you have to.

The most common way of user testing: participants are seated in front of a screen and asked to say what they see and feel. (Image source: iMotions) (Large preview)

There’s nothing really wrong with user testing this way except that it relies on the participant telling you (either during or after the session) why they did what they did, and how they feel about the product or app. You can see that they clicked on a particular button or touched a link on the mobile app, but if they explain why, you are only getting the conscious reason why.

People filter their feelings, decisions and reasons consciously.

What if you could get their unconscious reactions? What if you could take a look inside your users’ brains and see what it is they aren’t saying, i.e. the things they themselves may not realize about their reactions to your product?

We know that most mental processing — including decision-making and emotional reactions — occurs unconsciously. So if people tell you how they feel and why they did something, it is possible that they believe what they are saying is the truth, but it’s also possible that they don’t know how they feel or why they did or did not take an action.

People filter their feelings, decisions and reasons consciously and by that time you aren’t necessarily getting real data. Add to that the fact that users aren’t always truthful during user tests. They may not want to offend you by telling you they think your product is hard to use or boring.

So that’s why user researchers are starting to use some other tools to get reactions and data directly from the body without the filtering of conscious thought. Hence, biometrics and neuro-measurements.

Some of these new tools are easy and inexpensive to use. Others may take more investment of your time and budget. Or you may want to bring in an outside firm that specializes in these tools. (Some suggestions for outside vendors are at the end of the article.)

Let’s take a look at what’s available.

Galvanic Skin Response (GSR)

GSR is also called “electrodermal activity” or EDA. A typical GSR measurement device is a relatively small, unobtrusive sensor that is connected to the skin of your finger or hand.

Sweat glands on the hands are very sensitive to changes in your emotional state. If you become emotionally aroused — either positively or negatively — then you will release more sweat in your hands. Sometimes, these are very small changes that you may not notice. This is what a GSR monitor is measuring.

You may not notice that there is a small amount of moisture, but even the tiniest amount of increase in moisture changes the amount of electrical conductance of your skin. (Image source: iMotions) (Large preview)

The GSR monitor can’t tell if you are happy, sad, scared, and so on, but it can tell if you are becoming more or less emotional. And since the amount of sweat you release is not under conscious control, a GSR monitor can measure what you may not be consciously aware of.

GSR monitoring has been around for over a hundred years. The monitors are relatively inexpensive and easy to learn how to use. The price for a GSR monitor ranges from about $150 to $600, depending on the brand and model you get. If you want to buy your own, check out Carolina Supply. iMotions also has a great downloadable guide to GSR monitors that you can get for free.

Recommended reading: How People Make Decisions

Respiration

It’s also relatively easy to measure respiration. When people are emotionally aroused they breathe faster. This can be detected in several ways — the easiest being to place a cloth band around the chest and/or stomach and measure the expansion of the chest or stomach as people breathe.

A ‘respiration transducer’ helps measure any changes in the abdominal circumference that occur as a subject breathes. (Image source: iMotions) (Large preview)

If/when they are using your product and they start breathing faster, you can deduce that something has (either positively or negatively) affected them emotionally.

Heart Rate

You can also use the band around the chest or even a simpler measurement on a finger to measure heart rate/pulse. When you are emotionally aroused, your heart beats faster and your pulse increases.

How would you use GSR, respiration, or heart rate data in a user test or study? Let’s say you are testing an app for getting an insurance quote. You ask the user what they think of the insurance quote app, and they answer:

“It was OK, it wasn’t too hard to use.”

But looking at their GSR, respiration, and/or heart rate might tell you that they were stressed. The data will also show you when and where in the process they had the most stress.

Like GSR monitors, heart-rate and respiration monitors are relatively inexpensive (under $100). What you may really want, however, is a total package that includes, a universal monitor that you can plug more than one measurement into.

For example, you can use GSR, heart rate, respiration and even EEG (discussed below), plus software that lets you monitor the data and combine it with actions your users are taking at specific moments during your user study. These packages will cost you a lot, however. A whole system may run as much as $7,000.

To get started, you may want to bring in a vendor who has the equipment to get your feet wet before you decide to buy these tools for your lab.

Eye Tracking

I am probably unusual in my criticisms of eye-tracking. A lot of people like eye tracking, but I think it has some problems. I’ll explain why.

Eye tracking involves having people look at a special monitor while wearing eye-tracking headsets/glasses. The eye tracker measures what you look at and how long you look at it. If you were doing user testing on a web page, then you could see (either for an individual or through aggregated data) where people looked most, how long they looked at it, and what people did not look at, and so on.

Eye tracking works just fine in measuring what it is measuring. But here’s my criticism: Eye tracking only measures where people are looking with their central vision. It doesn’t measure peripheral vision.

Recent research on peripheral vision shows that peripheral vision is more important than once thought for information process. For example, images of danger and emotion are processed faster in peripheral vision than in central vision. We also know now that people use peripheral vision to decide if they are the right place, or in the case of software and website design, if they are at the right page or screen. It’s possible for people to “see” something in peripheral vision, but not be consciously aware that they have. And what they see can influence the action they take.

Since eye tracking doesn’t track any peripheral vision data, I am not a big fan of it. Monitors with eye tracking built in, plus the software to analyze and report on the data can cost around $7,000 to $10,000.

Eye tracking only measures where people are looking with their central and not with their peripheral vision.

“ Facial Coding

Cameras can capture someone’s face as they use a product or watch a video. Algorithms can then analyze the facial expressions and tell you whether the person is confused, happy, scared, and so on.

Facial coding uses algorithms to take a good guess at what the person is feeling. (Image source: iMotions) (Large preview)

Facial coding is also an “add-on” feature to eye tracking. You should assume similar pricing ($7,000 to $10,000) for facial coding as for eye tracking

fEMG

EMG stands for Electromyography, or muscle movement. Whenever a muscle contracts it generates a small amount of electricity which can be detected with some fairly simple electrodes. Muscle movement can be very small — you may not see the muscle move, but you can measure it.

This means that some of the most interesting EMG measurements come from the movement of muscles in the face or fEMG. Facial coding uses algorithms to take a good guess at what the person is feeling, but with fEMG you can actually measure the muscles in the face and thereby more accurately assess the emotion that the person is feeling. There is muscle activity in the face that a video won’t detect, but that the fEMG recordings will detect. This means that with fEMG you can pick up on emotions that are not being obviously displayed through just facial coding.

(Image source: iMotions) (Large preview)

When would you use facial coding or fEMG?

Well, let’s say you have created some new videos for the careers/employment page of your company’s website. The videos have real people who work at the company talking about how they came to be an employee, and what it is they like about working at the company. You want to know if people like and resonate with the videos. Facial coding and, even better, fEMG, would help you measure what people are feeling, and even tell you which parts of the video are eliciting which emotions.

fEMG equipment and software are expensive and not easy to learn how to use. For this reason, you will probably want to start by bringing in a vendor rather than using this on your own.

EEG (Electroencephalography)

You can directly measure the electrical activity of the brain by placing electrodes on the scalp. EEG devices measure the electrical activity generated by neurons.

EEG measures electrical changes on the surface of the brain — not deep within particular brain structures. This means that EEG can’t tell you that a particular part of the brain is active. It can only tell you when there is more or less brain activity. You would need to use more sophisticated methods, such as fMRI (functional Magnetic Resonance Imaging) to study more specific brain activity. fMRI equipment is very large and very expensive, which is why only research and medical institutions use them. In contrast, EEG is inexpensive.

EEG measures whether a person is engaged and paying attention. EEG measurements are particularly good at showing you activity by seconds or even parts of a second. Let’s go back to the example of the user test to measure the impact of the employee story videos at the careers/jobs page of the corporate website. Are the videos interesting? Do people pay attention while watching them? Exactly which parts of the videos are engaging? EEG can tell you this.

When I was in graduate school and doing EEG research, we had to use electrodes and gel to get EEG readings, but now there are easier ways. You can place a cap on someone’s head, kind of like a swim cap, and the electrodes are built in to the cap.

(Image source: iMotions) (Large preview)

Some devices are like headsets rather than swim caps:

(Image source: Spark Neuro) (Large preview)

EEG devices range from the inexpensive to the expensive. For example, Emotiv makes a $299 EEG headset. You will probably, however, want to get a higher end version for $799, and then you will need a subscription for the software ($99 a month).

It can take a while to learn how to accurately read EEG data, so, again, it might be better to start by bringing in a vendor who has all the equipment and know-how until you learn.

Recommended reading: Grabbing Visual Attention With The Visual Cortex

Combining Measurements

It is common to combine multiple methods of biometrics together to help with the accuracy and interpretation of the results.

Although biometrics and neuro-measurements don’t tell the whole story, the data that we get from biometrics and neuro-measurements is more accurate than self-reporting. As the tools become easier to use and researchers get used to using them, they will become more common. We may even get to the point where we stop using the think-aloud technique altogether, although I don’t think we are there yet!

Takeaways
  • If you haven’t already researched biometrics for your user testing projects, now is a good time to check out these measurements as an addition to your current testing.
  • Pick a modality and/or a vendor and do a trial project.
  • If you are in charge of user-testing budgets, add in some biometrics to your budgeting process for the next year or two so you can get started.
Vendors

Vendors to consider for a biometric study:

This article is part of the UX design series sponsored by Adobe. Adobe XD tool is made for a fast and fluid UX design process, as it lets you go from idea to prototype faster. Design, prototype and share — all in one app. You can check out more inspiring projects created with Adobe XD on Behance, and also sign up for the Adobe experience design newsletter to stay updated and informed on the latest trends and insights for UX/UI design.

(cm, ms, ra, il)
Categories: Around The Web

How To Build An Endless Runner Game In Virtual Reality (Part 1)

Smashing Magazine - Wed, 03/06/2019 - 9:00am
How To Build An Endless Runner Game In Virtual Reality (Part 1) How To Build An Endless Runner Game In Virtual Reality (Part 1) Alvin Wan 2019-03-06T14:00:35+01:00 2019-03-23T22:35:13+00:00

Today, I’d like to invite you to build an endless runner VR game with webVR — a framework that gives a dual advantage: It can be played with or without a VR headset. I’ll explain the magic behind the gaze-based controls for our VR-headset players by removing the game control’s dependence on a keyboard.

In this tutorial, I’ll also show you how you can synchronize the game state between two devices which will move you one step closer to building a multiplayer game. I’ll specifically introduce more A-Frame VR concepts such as stylized low-poly entities, lights, and animation.

To get started, you will need the following:

  • Internet access (specifically to glitch.com);
  • A new Glitch project;
  • A virtual reality headset (optional, recommended). (I use Google Cardboard, which is offered at $15 a piece.)

Note: A demo of the final product can be viewed here.

Step 1: Setting Up A Basic Scene

In this step, we will set up the following scene for our game. It is composed of a few basic geometric shapes and includes custom lighting, which we will describe in more detail below. As you progress in the tutorial, you will add various animations and effects to transform these basic geometric entities into icebergs sitting in an ocean.

A preview of the game scene’s basic geometric objects (Large preview)

You will start by setting up a website with a single static HTML page. This allows you to code from your desktop and automatically deploy to the web. The deployed website can then be loaded on your mobile phone and placed inside a VR headset. Alternatively, the deployed website can be loaded by a standalone VR headset.

Get started by navigating to glitch.com. Then, do the following:

  1. Click on “New Project” in the top right.
  2. Click on “hello-webpage” in the drop down. Glitch.com’s homepage (Large preview)
  3. Next, click on index.html in the left sidebar. We will refer to this as your “editor”.
Glitch project: the index.html file (Large preview)

Start by deleting all existing code in the current index.html file. Then, type in the following for a basic webVR project, using A-Frame VR. This creates an empty scene by using A-Frame’s default lighting and camera.

<!DOCTYPE html> <html> <head> <title>Ergo | Endless Runner Game in Virtual Reality</title> <script src="https://aframe.io/releases/0.7.0/aframe.min.js"></script> </head> <body> <a-scene> </a-scene> </body> </html>

Note: You can learn more about A-Frame VR at aframe.io.

To start, add a fog, which will obscure objects far away for us. Modify the a-scene tag on line 8.

<a-scene fog="type: linear; color: #a3d0ed; near:5; far:20">

Moving forward, all objects in the scene will be added between the <a-scene>...</a-scene> tags. The first item is the sky. Between your a-scene tags, add the a-sky entity.

<a-scene ...> <a-sky color="#a3d0ed"></a-sky> </a-scene>

After your sky, add lighting to replace the default A-Frame lighting.

There are three types of lighting:

  • Ambient This is an ever-present light that appears to emanate from all objects in the scene. If you wanted a blue tint on all objects, resulting in blue-ish shadows, you would add a blue ambient light. For example, the objects in this Low Poly Island scene are all white. However, a blue ambient light results in a blue hue.
  • Directional This is analogous to a flashlight which, as the name suggests, points in a certain direction.
  • Point Again, as the name suggests, this emanates light from a point.

Just below your a-sky entity, add the following lights: one directional and one ambient. Both are light blue.

<!-- Lights --> <a-light type="directional" castShadow="true" intensity="0.4" color="#D0EAF9;" position="5 3 1"></a-light> <a-light intensity="0.8" type="ambient" color="#B4C5EC"></a-light>

Next, add a camera with a custom position to replace the default A-Frame camera. Just below your a-light entities, add the following:

<!-- Camera --> <a-camera position="0 0 2.5"></a-camera>

Just below your a-camera entity, add several icebergs using low-poly cones.

<!-- Icebergs --> <a-cone class="iceberg" segments-radial="5" segments-height="3" height="1" radius-top="0.15" radius-bottom="0.5" position="3 -0.1 -1.5"></a-cone> <a-cone class="iceberg" segments-radial="7" segments-height="3" height="0.5" radius-top="0.25" radius-bottom="0.35" position="-3 -0.1 -0.5"></a-cone> <a-cone class="iceberg" segments-radial="6" segments-height="2" height="0.5" radius-top="0.25" radius-bottom="0.25" position="-5 -0.2 -3.5"></a-cone>

Next, add an ocean, which we will temporarily represent with a box, among your icebergs. In your code, add the following after the cones from above.

<!-- Ocean --> <a-box depth="50" width="50" height="1" color="#7AD2F7" position="0 -0.5 0"></a-box>

Next, add a platform for our endless runner game to take place on. We will represent this platform using the side of a large cone. After the box above, add the following:

<!-- Platform --> <a-cone scale="2 2 2" shadow position="0 -3.5 -1.5" rotation="90 0 0" radius-top="1.9" radius-bottom="1.9" segments-radial="20" segments-height="20" height="20" emissive="#005DED" emissive-intensity="0.1"> <a-entity id="tree-container" position="0 .5 -1.5" rotation="-90 0 0"> </a-entity> </a-cone>

Finally, add the player, which we will represent using a small glowing sphere, on the platform we just created. Between the <a-entity id="tree-container" ...></a-entity> tags, add the following:

<a-entity id="tree-container"...> <!-- Player --> <a-entity id="player" player> <a-sphere radius="0.05"> <a-light type="point" intensity="0.35" color="#FF440C"></a-light> </a-sphere> </a-entity> </a-entity>

Check that your code now matches the following, exactly. You can also view the full source code for step 1.

<!DOCTYPE html> <html> <head> <title>Ergo | Endless Runner Game in Virtual Reality</title> <script src="https://aframe.io/releases/0.7.0/aframe.min.js"></script> </head> <body> <a-scene fog="type: linear; color: #a3d0ed; near:5; far:20"> <a-sky color="#a3d0ed"></a-sky> <!-- Lights --> <a-light type="directional" castShadow="true" intensity="0.4" color="#D0EAF9;" position="5 3 1"></a-light> <a-light intensity="0.8" type="ambient" color="#B4C5EC"></a-light> <!-- Camera --> <a-camera position="0 0 2.5"></a-camera> <!-- Icebergs --> <a-cone class="iceberg" segments-radial="5" segments-height="3" height="1" radius-top="0.15" radius-bottom="0.5" position="3 -0.1 -1.5"></a-cone> <a-cone class="iceberg" segments-radial="7" segments-height="3" height="0.5" radius-top="0.25" radius-bottom="0.35" position="-3 -0.1 -0.5"></a-cone> <a-cone class="iceberg" segments-radial="6" segments-height="2" height="0.5" radius-top="0.25" radius-bottom="0.25" position="-5 -0.2 -3.5"></a-cone> <!-- Ocean --> <a-box depth="50" width="50" height="1" color="#7AD2F7" position="0 -0.5 0"></a-box> <!-- Platform --> <a-cone scale="2 2 2" shadow position="0 -3.5 -1.5" rotation="90 0 0" radius-top="1.9" radius-bottom="1.9" segments-radial="20" segments-height="20" height="20" emissive="#005DED" emissive-intensity="0.1"> <a-entity id="tree-container" position="0 .5 -1.5" rotation="-90 0 0"> <!-- Player --> <a-entity id="player" player> <a-sphere radius="0.05"> <a-light type="point" intensity="0.35" color="#FF440C"></a-light> </a-sphere> </a-entity> </a-entity> </a-cone> </a-scene> </body> </html>

To preview the webpage, click on “Preview” in the top left. We will refer to this as your preview. Note that any changes in your editor will be automatically reflected in this preview, barring bugs or unsupported browsers.

“Show Live” button in glitch project (Large preview)

In your preview, you will see the following basic virtual reality scene. You can view this scene by using your favorite VR headset.

Animating Ocean and the fixed white cursor (Large preview)

This concludes the first step, setting up the game scene’s basic geometric objects. In the next step, you will add animations and use other A-Frame VR libraries for more visual effects.

Step 2: Improve Aesthetics for Virtual Reality Scene

In this step, you will add a number of aesthetic improvements to the scene:

  1. Low-poly objects You will substitute some of the basic geometric objects with their low-poly equivalents for more convincing, irregular geometric shapes.
  2. Animations You will have the player bob up and down, move the icebergs slightly, and make the ocean a moving body of water.

Your final product for this step will match the following:

Low-poly icebergs bobbing around (Large preview)

To start, import A-Frame low-poly components. In <head>...</head>, add the following JavaScript import:

<script src="https://aframe.io...></script> <script src="https://cdn.jsdelivr.net/gh/alvinwan/aframe-low-poly@0.0.2/dist/aframe-low-poly.min.js"></script> </head>

The A-Frame low-poly library implements a number primitives, such as lp-cone and lp-sphere, each of which is a low-poly version of an A-Frame primitive. You can learn more about A-Frame primitives over here.

Next, navigate to the <!-- Icebergs --> section of your code. Replace all <a-cone>s with <lp-cone>.

<!-- Icebergs --> <lp-cone class="iceberg" ...></lp-cone> <lp-cone class="iceberg" ...></lp-cone> <lp-cone class="iceberg" ...></lp-cone>

We will now configure the low-poly primitives. All low-poly primitive supports two attributes, which control how exaggerated the low-poly stylization is:

  1. amplitude This is the degree of stylization. The greater this number, the more a low-poly shape can deviate from its original geometry.
  2. amplitude-variance This is how much stylization can vary, from vertex to vertex. The greater this number, the more variety there is in how much each vertex may deviate from its original geometry.

To get a better intuition for what these two variables mean, you can modify these two attributes in the A-Frame low-poly demo.

For the first iceberg, set amplitude-variance to 0.25. For the second iceberg, set amplitude to 0.12. For the last iceberg, set amplitude to 0.1.

<!-- Icebergs --> <lp-cone class="iceberg" amplitude-variance="0.25" ...></lp-cone> <lp-cone class="iceberg" amplitude="0.12" ... ></lp-cone> <lp-cone class="iceberg" amplitude="0.1" ...></lp-cone>

To finish the icebergs, animate both position and rotation for all three icebergs. Feel free to configure these positions and rotations as desired.

The below features a sample setting:

<lp-cone class="iceberg" amplitude-variance="0.25" ...> <a-animation attribute="rotation" from="-5 0 0" to="5 0 0" repeat="indefinite" direction="alternate"></a-animation> <a-animation attribute="position" from="3 -0.2 -1.5" to="4 -0.2 -2.5" repeat="indefinite" direction="alternate" dur="12000" easing="linear"></a-animation> </lp-cone> <lp-cone class="iceberg" amplitude="0.12" ...> <a-animation attribute="rotation" from="0 0 -5" to="5 0 0" repeat="indefinite" direction="alternate" dur="1500"></a-animation> <a-animation attribute="position" from="-4 -0.2 -0.5" to="-2 -0.2 -0.5" repeat="indefinite" direction="alternate" dur="15000" easing="linear"></a-animation> </lp-cone> <lp-cone class="iceberg" amplitude="0.1" ...> <a-animation attribute="rotation" from="5 0 -5" to="5 0 0" repeat="indefinite" direction="alternate" dur="800"></a-animation> <a-animation attribute="position" from="-3 -0.2 -3.5" to="-5 -0.2 -5.5" repeat="indefinite" direction="alternate" dur="15000" easing="linear"></a-animation> </lp-cone>

Navigate to your preview, and you should see the low-poly icebergs bobbing around.

Bobbing player with fluctuating light (Large preview)

Next, update the platform and associated player. Here, upgrade the cone to a low-poly object, changing a-cone to lp-cone for <!-- Platform -->. Additionally, add configurations for amplitude.

<!-- Platform --> <lp-cone amplitude="0.05" amplitude-variance="0.05" scale="2 2 2"...> ... </lp-cone>

Next, still within the platform section, navigate to the <!-- Player --> subsection of your code. Add the following animations for position, size, and intensity.

<!-- Player --> <a-entity id="player" ...> <a-sphere ...> <a-animation repeat="indefinite" direction="alternate" attribute="position" ease="ease-in-out" from="0 0.5 0.6" to="0 0.525 0.6"></a-animation> <a-animation repeat="indefinite" direction="alternate" attribute="radius" from="0.05" to="0.055" dur="1500"></a-animation> <a-light ...> <a-animation repeat="indefinite" direction="alternate-reverse" attribute="intensity" ease="ease-in-out" from="0.35" to="0.5"></a-animation> </a-light> </a-sphere> </a-entity>

Navigate to your preview, and you will see your player bobbing up and down, with a fluctuating light on a low-poly platform.

Bobbing player with fluctuating light (Large preview)

Next, let’s animate the ocean. Here, you can use a lightly-modified version of Don McCurdy’s ocean. The modifications allow us to configure how large and fast the ocean’s waves move.

Create a new file via the Glitch interface, by clicking on “+ New File” on the left. Name this new file assets/ocean.js. Paste the following into your new ocean.js file:

/** * Flat-shaded ocean primitive. * https://github.com/donmccurdy/aframe-extras * * Based on a Codrops tutorial: * http://tympanus.net/codrops/2016/04/26/the-aviator-animating-basic-3d-scene-threejs/ */ AFRAME.registerPrimitive('a-ocean', { defaultComponents: { ocean: {}, rotation: {x: -90, y: 0, z: 0} }, mappings: { width: 'ocean.width', depth: 'ocean.depth', density: 'ocean.density', amplitude: 'ocean.amplitude', 'amplitude-variance': 'ocean.amplitudeVariance', speed: 'ocean.speed', 'speed-variance': 'ocean.speedVariance', color: 'ocean.color', opacity: 'ocean.opacity' } }); AFRAME.registerComponent('ocean', { schema: { // Dimensions of the ocean area. width: {default: 10, min: 0}, depth: {default: 10, min: 0}, // Density of waves. density: {default: 10}, // Wave amplitude and variance. amplitude: {default: 0.1}, amplitudeVariance: {default: 0.3}, // Wave speed and variance. speed: {default: 1}, speedVariance: {default: 2}, // Material. color: {default: '#7AD2F7', type: 'color'}, opacity: {default: 0.8} }, /** * Use play() instead of init(), because component mappings – unavailable as dependencies – are * not guaranteed to have parsed when this component is initialized. * / play: function () { const el = this.el, data = this.data; let material = el.components.material; const geometry = new THREE.PlaneGeometry(data.width, data.depth, data.density, data.density); geometry.mergeVertices(); this.waves = []; for (let v, i = 0, l = geometry.vertices.length; i < l; i++) { v = geometry.vertices[i]; this.waves.push({ z: v.z, ang: Math.random() * Math.PI * 2, amp: data.amplitude + Math.random() * data.amplitudeVariance, speed: (data.speed + Math.random() * data.speedVariance) / 1000 // radians / frame }); } if (!material) { material = {}; material.material = new THREE.MeshPhongMaterial({ color: data.color, transparent: data.opacity < 1, opacity: data.opacity, shading: THREE.FlatShading, }); } this.mesh = new THREE.Mesh(geometry, material.material); el.setObject3D('mesh', this.mesh); }, remove: function () { this.el.removeObject3D('mesh'); }, tick: function (t, dt) { if (!dt) return; const verts = this.mesh.geometry.vertices; for (let v, vprops, i = 0; (v = verts[i]); i++){ vprops = this.waves[i]; v.z = vprops.z + Math.sin(vprops.ang) * vprops.amp; vprops.ang += vprops.speed * dt; } this.mesh.geometry.verticesNeedUpdate = true; } });

Navigate back to your index.html file. In the <head> of your code, import the new JavaScript file:

<script src="https://cdn.jsdelivr.net..."></script> <script src="./assets/ocean.js"></script> </head>

Navigate to the <!-- Ocean --> section of your code. Replace the a-box to an a-ocean. Just as before, we set amplitude and amplitude-variance of our low-poly object.

<!-- Ocean --> <a-ocean depth="50" width="50" amplitude="0" amplitude-variance="0.1" speed="1.5" speed-variance="1" opacity="1" density="50"></a-ocean> <a-ocean depth="50" width="50" opacity="0.5" amplitude="0" amplitude-variance="0.15" speed="1.5" speed-variance="1" density="50"></a-ocean>

For your final aesthetic modification, add a white round cursor to indicate where the user is pointing. Navigate to the <!-- Camera -->.

<!-- Camera --> <a-camera ...> <a-entity id="cursor-mobile" cursor="fuse: true; fuseTimeout: 250" position="0 0 -1" geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03" material="color: white; shader: flat" scale="0.5 0.5 0.5" raycaster="far: 50; interval: 1000; objects: .clickable"> <a-animation begin="fusing" easing="ease-in" attribute="scale" fill="backwards" from="1 1 1" to="0.2 0.2 0.2" dur="250"></a-animation> </a-camera>

Ensure that your index.html code matches the Step 2 source code. Navigate to your preview, and you’ll find the updated ocean along with a white circle fixed to the center of your view.

Bobbing player with fluctuating light (Large preview)

This concludes your aesthetic improvements to the scene. In this section, you learned how to use and configure low-poly versions of A-Frame primitives, e.g. lp-cone. In addition, you added a number of animations for different object attributes, such as position, rotation, and light intensity. In the next step, you will add the ability for the user to control the player — just by looking at different lanes.

Step 3: Add Virtual Reality Gaze Controls

Recall that our audience is a user wearing a virtual reality headset. As a result, your game cannot depend on keyboard input for controls. To make this game accessible, our VR controls will rely only on the user’s head rotation. Simply look to the right to move the player to the right, look to the center to move to the middle, and look to the left to move to the left. Our final product will look like the following.

Note: The demo GIF below was recorded on a desktop, with user drag as a substitute for head rotation.

Controlling game character with head rotation (Large preview)

Start from your index.html file. In the <head>...</head> tag, import your new JavaScript file, assets/ergo.js. This new JavaScript file will contain the game’s logic.

<script src=...></script> <script src="./assets/ergo.js"></script> </head>

Then, add a new lane-controls attribute to your a-camera object:

<!-- Camera --> <a-camera lane-controls position...> </a-camera>

Next, create your new JavaScript file using “+ New File” to the left. Use assets/ergo.js for the filename. For the remainder of this step, you will be working in this new JavaScript file. In this new file, define a new function to setup controls, and invoke it immediately. Make sure to include the comments below, as we will refer to sections of code by those names.

/************ * CONTROLS * ************/ function setupControls() { } /******** * GAME * ********/ setupControls();

Note: The setupControls function is invoked in the global scope, because A-Frame components must be registered before the <a-scene> tag. I will explain what a component is below.

In your setupControls function, register a new A-Frame component. A component modifies an entity in A-Frame, allowing you to add custom animations, change how an entity initializes, or respond to user input. There are many other use cases, but you will focus on the last one: responding to user input. Specifically, you will read user rotation and move the player accordingly.

In the setupControls function, register the A-Frame component we added to the camera earlier, lane-controls. We will add an event listener for the tick event. This event triggers at every animation frame. In this event listener, hlog output at every tick.

function setupControls() { AFRAME.registerComponent('lane-controls', { tick: function(time, timeDelta) { console.log(time); } }); }

Navigate to your preview. Open your browser developer console by right-clicking anywhere and selecting “Inspect”. This applies to Firefox, Chrome, and Safari. Then, select “Console” from the top navigation bar. Ensure that you see timestamps flowing into the console.

Timestamps in console (Large preview)

Navigate back to your editor. Still in assets/ergo.js, replace the body of setupControls with the following. Fetch the camera rotation using this.el.object3D.rotation, and log the lane to move the player to.

function setupControls() { AFRAME.registerComponent('lane-controls', { tick: function (time, timeDelta) { var rotation = this.el.object3D.rotation; if (rotation.y > 0.1) console.log("left"); else if (rotation.y < -0.1) console.log("right"); else console.log("middle"); } }) }

Navigate back to your preview. Again, open your developer console. Try rotating the camera slightly, and observe console output update accordingly.

Lane log based on camera rotation (Large preview)

Before the controls section, add three constants representing the left, middle, and right lane x values.

const POSITION_X_LEFT = -0.5; const POSITION_X_CENTER = 0; const POSITION_X_RIGHT = 0.5; /************ * CONTROLS * ************/ ...

At the start of the controls section, define a new global variable representing the player position.

/************ * CONTROLS * ************/ // Position is one of 0 (left), 1 (center), or 2 (right) var player_position_index = 1; function setupControls() { ...

After the new global variable, define a new function that will move the player to each lane.

var player_position_index = 1; /** * Move player to provided index * @param {int} Lane to move player to */ function movePlayerTo(position_index) { } function setupControls() { ...

Inside this new function, start by updating the global variable. Then, define a dummy position.

function movePlayerTo(position_index) { player_position_index = position_index; var position = {x: 0, y: 0, z: 0} }

After defining the position, update it according to the function input.

function movePlayerTo(position_index) { ... if (position_index == 0) position.x = POSITION_X_LEFT; else if (position_index == 1) position.x = POSITION_X_CENTER; else position.x = POSITION_X_RIGHT; }

Finally, update the player position.

function movePlayerTo(position_index) { ... document.getElementById('player').setAttribute('position', position); }

Double-check that your function matches the following.

/** * Move player to provided index * @param {int} Lane to move player to */ function movePlayerTo(position_index) { player_position_index = position_index; var position = {x: 0, y: 0, z: 0} if (position_index == 0) position.x = POSITION_X_LEFT; else if (position_index == 1) position.x = POSITION_X_CENTER; else position.x = POSITION_X_RIGHT; document.getElementById('player').setAttribute('position', position); }

Navigate back to your preview. Open the developer console. Invoke your new movePlayerTo function from the console to ensure that it functions.

> movePlayerTo(2) # should move to right

Navigate back to your editor. For the final step, update your setupControls to move the player depending on camera rotation. Here, we replace the console.log with movePlayerTo invocations.

function setupControls() { AFRAME.registerComponent('lane-controls', { tick: function (time, timeDelta) { var rotation = this.el.object3D.rotation; if (rotation.y > 0.1) movePlayerTo(0); else if (rotation.y < -0.1) movePlayerTo(2); else movePlayerTo(1); } }) }

Ensure that your assets/ergo.js matches the corresponding file in the Step 3 source code. Navigate back to your preview. Rotate the camera from side to side, and your player will now track the user’s rotation.

Controlling game character with head rotation (Large preview)

This concludes gaze controls for your virtual reality endless runner game.

In this section, we learned how to use A-Frame components and saw how to modify A-Frame entity properties. This also concludes part 1 of our endless runner game tutorial. You now have a virtual reality model equipped with aesthetic improvements like low-poly stylization and animations, in addition to a virtual-reality-headset-friendly gaze control for players to use.

Conclusion

We created a simple, interactive virtual reality model, as a start for our VR endless runner game. We covered a number of A-Frame concepts such as primitives, animations, and components — all of which are necessary for building a game on top of A-Frame VR.

Here are extra resources and next steps for working more with these technologies:

  • A-Frame VR Official documentation for A-Frame VR, covering the topics used above in more detail.
  • A-Frame Homepage Examples of A-Frame projects, exhibiting different A-Frame capabilities.
  • Low-Poly Island VR model using the same lighting, textures, and animations as the ones used for this endless runner game.

In the next part of this article series, I’ll show you how you can implement the game’s core logic and use more advanced A-Frame VR scene manipulations in JavaScript.

(rb, ra, il)
Categories: Around The Web

Building Robust Layouts With Container Units

Smashing Magazine - Tue, 03/05/2019 - 10:00am
Building Robust Layouts With Container Units Building Robust Layouts With Container Units Russell Bishop 2019-03-05T15:00:17+01:00 2019-03-23T22:35:13+00:00

Container units are a specialized set of CSS variables that allow you to build grids, layouts, and components using columns and gutters. They mirror the layout functionality found in UI design software where configuring just three values provides your document with a global set of columns and gutters to measure and calculate from.

They also provide consistent widths everywhere in your document — regardless of their nesting depth, their parent’s width, or their sibling elements. So instead of requiring a repeated set of .grid and .row parent elements, container units measure from the :root of your document — just like using a rem unit.

(Large preview) What Makes Container Units Different?

Grids from popular frameworks (such as Bootstrap or Bulma) share the same fundamental limitation: they rely on relative units such as ‘percentages’ to build columns and gutters.

This approach ties developers to using a specific HTML structure whenever they want to use those measurements and requires parent > child nesting for widths to calculate correctly.

Not convinced? Try for yourself:

  • Open any CSS framework’s grid demo;
  • Inspect a column and note the width;
  • Using DevTools, drag that element somewhere else in the document;
  • Note that the column’s width has changed in transit.
Freedom Of Movement (…Not Brexit)

Container units allow you more freedom to size elements using a set of global units. If you want to build a sidebar the width of three columns, all you need is the following:

.sidebar { width: calc(3 * var(--column-unit)); /* or columns(3) */ }

Your ...class="sidebar">... element can live anywhere inside of your document — without specific parent elements or nesting.

Measuring three columns and using them for a sidebar (Large preview) Sharing Tools With Designers

Designers and developers have an excellent middle-ground that helps translate from design software to frontend templates: numbers.

Modular scales are exceptional not just because they help designers bring harmony to their typography, but also because developers can replicate them as a simple system. The same goes for Baseline Grids: superb, self-documenting systems with tiny configuration (one root number) and massive consistency.

Container units are set up in the same way that designers use Sketch to configure Layout Settings:

Layout settings (Large preview) Sketch gridlines (Large preview)

Any opportunity for designers and developers to build with the same tools is a huge efficiency boost and fosters new thinking in both specialisms.

Start Building With Container Units

Define your grid proportions with three values:

:root { --grid-width: 960; --grid-column-width: 60; --grid-columns: 12; }

These three values define how wide a column is in proportion to your grid. In the example above, a column’s width is 60 / 960. Gutters are calculated automatically from the remaining space.

Finally, set a width for your container:

:root { --container-width: 84vw; }

Note: --container-width should be set as an absolute unit. I recommend using viewport units or rems.

You can update your --container-width at any breakpoint (all of your container units will update accordingly):

@media (min-width: 800px) { --container-width: 90vw; } @media (min-width: 1200px) { --container-width: 85vw; } /* what about max-width? */ @media (min-width: 1400px) { --container-width: 1200px; } Breakpoints (Large preview)

You’ve now unlocked two very robust units to build from:

  1. --column-unit
  2. --gutter-unit
Column Spans: The Third And Final Weapon

More common than building with either columns or gutters is to span across both of them:

6 column span = 6 columns + 5 gutters (Large preview)

Column spans are easy to calculate, but not very pretty to write. For spanning across columns, I would recommend using a pre-processor:

.panel { /* vanilla css */ width: calc(6 * var(--column-and-gutter-unit) - var(--gutter-unit)); /* pre-processor shortcut */ width: column-spans(6); }

Of course, you can use pre-processor shortcuts for every container unit I’ve mentioned so far. Let’s put them to the test with a design example.

Building Components With Container Units

Let’s take a design example and break it down:

(Large preview)

This example uses columns, gutters and column spans. Since we’re just storing a value, container units can be used for other CSS properties, like defining a height or providing padding:

.background-image { width: column-spans(9); padding-bottom: gutters(6); /* 6 gutters taller than the foreground banner */ } .foreground-banner { width: column-spans(8); padding: gutters(2); } .button { height: gutters(3); padding: gutters(1); } Grab The Code :root { /* Grid proportions */ --grid-width: 960; --grid-column-width: 60; --grid-columns: 12; /* Grid logic */ --grid-gutters: calc(var(--grid-columns) - 1); /* Grid proportion logic */ --column-proportion: calc(var(--grid-column-width) / var(--grid-width)); --gutter-proportion: calc((1 - (var(--grid-columns) * var(--column-proportion))) / var(--grid-gutters)); /* Container Units */ --column-unit: calc(var(--column-proportion) * var(--container-width)); --gutter-unit: calc(var(--gutter-proportion) * var(--container-width)); --column-and-gutter-unit: calc(var(--column-unit) + var(--gutter-unit)); /* Container Width */ --container-width: 80vw; } @media (min-width: 1000px) { :root { --container-width: 90vw; } } @media (min-width: 1400px) { :root { --container-width: 1300px; } } Why Use CSS Variables? “Pre-processors have been able to do that for years with $variables — why do you need CSS variables?”

Not… quite. Although you can use variables to run calculations, you cannot avoid compiling unnecessary code when one of the variables updates it’s value.

Let’s take the following condensed example of a grid:

.grid { $columns: 2; $gutter: $columns * 1rem; display: grid; grid-template-columns: repeat($columns, 1fr); grid-gap: $gutter; @media (min-width: $medium) { $columns: 3; grid-template-columns: repeat($columns, 1fr); grid-gap: $gutter; } @media (min-width: $large) { $columns: 4; grid-template-columns: repeat($columns, 1fr); grid-gap: $gutter; } }

This example shows how every reference to a SASS/LESS variable has to be re-compiled if the variable changes — duplicating code over and over for each instance.

But CSS Variables share their logic with the browser, so browsers can do the updating for you.

.grid { --columns: 2; --gutter: calc(var(--columns) * 1rem); display: grid; grid-template-columns: repeat(var(--columns), 1fr); grid-gap: var(--gutter); @media (min-width: $medium) { --columns: 3; } @media (min-width: $large) { --columns: 4; } }

This concept helps form the logic of container units; by storing logic once at the root, every element in your document watches those values as they update, and responds accordingly.

Give it a try!

Recommended Reading (dm, ra, il)
Categories: Around The Web

Using Composer With WordPress

Smashing Magazine - Mon, 03/04/2019 - 9:00am
Using Composer With WordPress Using Composer With WordPress Leonardo Losoviz 2019-03-04T14:00:33+01:00 2019-03-22T10:35:03+00:00

WordPress is getting modernized. The recent inclusion of JavaScript-based Gutenberg as part of the core has added modern capabilities for building sites on the frontend, and the upcoming bump of PHP’s minimum version, from the current 5.2.4 to 5.6 in April 2019 and 7.0 in December 2019, will make available a myriad of new features to build powerful sites.

In my previous article on Smashing in which I identified the PHP features newly available to WordPress, I argued that the time is ripe to make components the basic unit for building functionalities in WordPress. On one side, Gutenberg already makes the block (which is a high-level component) the basic unit to build the webpage on the frontend; on the other side, by bumping up the required minimum version of PHP, the WordPress backend has access to the whole collection of PHP’s Object-Oriented Programming features (such as classes and objects, interfaces, traits and namespaces), which are all part of the toolset to think/code in components.

So, why components? What’s so great about them? A “component” is not an implementation (such as a React component), but instead, it’s a concept: It represents the act of encapsulating properties inside objects, and grouping objects together into a package which solves a specific problem. Components can be implemented for both the frontend (like those coded through JavaScript libraries such as React or Vue, or CSS component libraries such as Bootstrap) and the backend.

We can use already-created components and customize them for our projects, so we will boost our productivity by not having to reinvent the wheel each single time, and because of their focus on solving a specific issue and being naturally decoupled from the application, they can be tested and bug-fixed very easily, thus making the application more maintainable in the long term.

The concept of components can be employed for different uses, so we need to make sure we are talking about the same use case. In a previous article, I described how to componentize a website; the goal was to transform the webpage into a series of components, wrapping each other from a single topmost component all the way down to the most basic components (to render the layout). In that case, the use case for the component is for rendering — similar to a React component but coded in the backend. In this article, though, the use case for components is importing and managing functionality into the application.

Introduction To Composer And Packagist

To import and manage own and third-party components into our PHP projects, we can rely on the PHP-dependency manager Composer which by default retrieves packages from the PHP package repository Packagist (where a package is essentially a directory containing PHP code). With their ease of use and exceptional features, Composer + Packagist have become key tools for establishing the foundations of PHP-based applications.

Composer allows to declare the libraries the project depends on and it will manage (install/update) them. It works recursively: libraries depended-upon by dependencies will be imported to the project and managed too. Composer has a mechanism to resolve conflicts: If two different libraries depend on a different version of a same library, Composer will try to find a version that is compatible with both requirements, or raise an error if not possible.

To use Composer, the project simply needs a composer.json file in its root folder. This file defines the dependencies of the project (each for a specific version constraint based on semantic versioning) and may contain other metadata as well. For instance, the following composer.json file makes a project require nesbot/carbon, a library providing an extension for DateTime, for the latest patch of its version 2.12:

{ "require": { "nesbot/carbon": "2.12.*" } }

We can edit this file manually, or it can be created/updated through commands. For the case above, we simply open a terminal window, head to the project’s root directory, and type:

composer require "nesbot/carbon"

This command will search for the required library in Packagist (which is found here) and add its latest version as a dependency on the existing composer.json file. (If this file doesn’t yet exist, it will first create it.) Then, we can import the dependencies into the project, which are by default added under the vendor/ folder, by simply executing:

composer install

Whenever a dependency is updated, for instance nesbot/carbon released version 2.12.1 and the currently installed one is 2.12.0, then Composer will take care of importing the corresponding library by executing:

composer update

If we are using Git, we only have to specify the vendor/ folder on the .gitignore file to not commit the project dependencies under version control, making it a breeze to keep our project’s code thoroughly decoupled from external libraries.

Composer offers plenty of additional features, which are properly described in the documentation. However, already in its most basic use, Composer gives developers unlimited power for managing the project’s dependencies.

Introduction To WPackagist

Similar to Packagist, WPackagist is a PHP package repository. However, it comes with one particularity: It contains all the themes and plugins hosted on the WordPress plugin and theme directories, making them available to be managed through Composer.

To use WPackagist, our composer.json file must include the following information:

{ "repositories":[ { "type":"composer", "url":"https://wpackagist.org" } ] }

Then, any theme and plugin can be imported to the project by using "wpackagist-theme" and "wpackagist-plugin" respectively as the vendor name, and the slug of the theme or plugin under the WordPress directory (such as "akismet" in https://wordpress.org/plugins/akismet/) as the package name. Because themes do not have a trunk version, then the theme’s version constraint is recommended to be “*”:

{ "require": { "wpackagist-plugin/akismet":"^4.1", "wpackagist-plugin/bbpress":">=2.5.12", "wpackagist-theme/twentynineteen":"*" } }

Packages available in WPackagist have been given the type “wordpress-plugin” or “wordpress-theme”. As a consequence, after running composer update, instead of installing the corresponding themes and plugins under the default folder vendor/, these will be installed where WordPress expects them: under folders wp-content/themes/ and wp-content/plugins/ respectively.

Possibilities And Limitations Of Using WordPress And Composer Together

So far, so good: Composer makes it a breeze to manage a PHP project’s dependencies. However, WordPress’ core hasn’t adopted it as its dependency management tool of choice, primarily because WordPress is a legacy application that was never designed to be used with Composer, and the community can’t agree if WordPress should be considered the site or a site’s dependency, and integrating these approaches requires hacks.

In this concern, WordPress is outperformed by newer frameworks which could incorporate Composer as part of their architecture. For instance, Laravel underwent a major rewriting in 2013 to establish Composer as an application-level package manager. As a consequence, WordPress’ core still does not include the composer.json file required to manage WordPress as a Composer dependency.

Knowing that WordPress can’t be natively managed through Composer, let’s explore the ways such support can be added, and what roadblocks we encounter in each case.

There are three basic ways in which WordPress and Composer can work together:

  1. Manage dependencies when developing a theme or a plugin;
  2. Manage themes and plugins on a site;
  3. Manage the site completely (including its themes, plugins and WordPress’ core).

And there are two basic situations concerning who will have access to the software (a theme or plugin, or the site):

  1. The developer can have absolute control of how the software will be updated, e.g. by managing the site for the client, or providing training on how to do it;
  2. The developer doesn’t have absolute control of the admin user experience, e.g. by releasing themes or plugins through the WordPress directory, which will be used by an unknown party.

From the combination of these variables, we will have more or less freedom in how deep we can integrate WordPress and Composer together.

From a philosophical aspect concerning the objective and target group of each tool, while Composer empowers developers, WordPress focuses primarily on the needs of the end users first, and only then on the needs of the developers. This situation is not self-contradictory: For instance, a developer can create and launch the website using Composer, and then hand the site over to the end user who (from that moment on) will use the standard procedures for installing themes and plugins — bypassing Composer. However, then the site and its composer.json file fall out of sync, and the project can’t be managed reliably through Composer any longer: Manually deleting all plugins from the wp-content/plugins/ folder and executing composer update will not re-download those plugins added by the end user.

The alternative to keeping the project in sync would be to ask the user to install themes and plugins through Composer. However, this approach goes against WordPress’ philosophy: Asking the end user to execute a command such as composer install to install the dependencies from a theme or plugin adds friction, and WordPress can’t expect every user to be able to execute this task, as simple as it may be. So this approach can’t be the default; instead, it can be used only if we have absolute control of the user experience under wp-admin/, such as when building a site for our own client and providing training on how to update the site.

The default approach, which handles the case when the party using the software is unknown, is to release themes and plugins with all of their dependencies bundled in. This implies that the dependencies must also be uploaded to WordPress’ plugin and theme subversion repositories, defeating the purpose of Composer. Following this approach, developers are still able to use Composer for development, however, not for releasing the software.

This approach is not failsafe either: If two different plugins bundle different versions of a same library which are incompatible with each other, and these two plugins are installed on the same site, it could cause the site to malfunction. A solution to this issue is to modify the dependencies’ namespace to some custom namespace, which ensures that different versions of the same library, by having different namespaces, are treated as different libraries. This can be achieved through a custom script or through Mozart, a library which composes all dependencies as a package inside a WordPress plugin.

For managing the site completely, Composer must install WordPress under a subdirectory as to be able to install and update WordPress’ core without affecting other libraries, hence the setup must consider WordPress as a site’s dependency and not the site itself. (Composer doesn’t take a stance: This decision is for the practical purpose of being able to use the tool; from a theoretical perspective, we can still consider WordPress to be the site.) Because WordPress can be installed in a subdirectory, this doesn’t represent a technical issue. However, WordPress is by default installed on the root folder, and installing it in a subdirectory involves a conscious decision taken by the user.

To make it easier to completely manage WordPress with Composer, several projects have taken the stance of installing WordPress in a subfolder and providing an opinionated composer.json file with a setup that works well: core contributor John P. Bloch provides a mirror of WordPress’ core, and Roots provides a WordPress boilerplate called Bedrock. I will describe how to use each of these two projects in the sections below.

Managing The Whole WordPress Site Through John P. Bloch’s Mirror Of WordPress Core

I have followed Andrey “Rarst” Savchenko’s recipe for creating the whole site’s Composer package, which makes use of John P. Bloch’s mirror of WordPress’ core. Following, I will reproduce his method, adding some extra information and mentioning the gotchas I found along the way.

First, create a composer.json file with the following content in the root folder of your project:

{ "type": "project", "config": { "vendor-dir": "content/vendor" }, "extra": { "wordpress-install-dir": "wp" }, "require": { "johnpbloch/wordpress": ">=5.1" } }

Through this configuration, Composer will install WordPress 5.1 under folder "wp", and dependencies will be installed under folder "content/vendor". Then head to the project’s root folder in terminal and execute the following command for Composer to do its magic and install all dependencies, including WordPress:

composer install --prefer-dist

Let’s next add a couple of plugins and the theme, for which we must also add WPackagist as a repository, and let’s configure these to be installed under "content/plugins" and "content/themes" respectively. Because these are not the default locations expected by WordPress, we will later on need to tell WordPress where to find them through constant WP_CONTENT_DIR.

Note: WordPress’ core includes by default a few themes and plugins under folders "wp/wp-content/themes" and "wp/wp-content/plugins", however, these will not be accessed.

Add the following content to composer.json, in addition to the previous one:

{ "repositories": [ { "type": "composer", "url" : "https://wpackagist.org" } ], "require": { "wpackagist-plugin/wp-super-cache": "1.6.*", "wpackagist-plugin/bbpress": "2.5.*", "wpackagist-theme/twentynineteen": "*" }, "extra": { "installer-paths": { "content/plugins/{$name}/": ["type:wordpress-plugin"], "content/themes/{$name}/": ["type:wordpress-theme"] } } }

And then execute in terminal:

composer update --prefer-dist

Hallelujah! The theme and plugins have been installed! Since all dependencies are distributed across folders wp, content/vendors, content/plugins and content/themes, we can easily ignore these when committing our project under version control through Git. For this, create a .gitignore file with this content:

wp/ content/vendor/ content/themes/ content/plugins/

Note: We could also directly ignore folder content/, which will already ignore all media files under content/uploads/ and files generated by plugins, which most likely must not go under version control.

There are a few things left to do before we can access the site. First, duplicate the wp/wp-config-sample.php file into wp-config.php (and add a line with wp-config.php to the .gitignore file to avoid committing it, since this file contains environment information), and edit it with the usual information required by WordPress (database information and secret keys and salts). Then, add the following lines at the top of wp-config.php, which will load Composer’s autoloader and will set constant WP_CONTENT_DIR to folder content/:

// Load Composer’s autoloader require_once (__DIR__.'/content/vendor/autoload.php'); // Move the location of the content dir define('WP_CONTENT_DIR', dirname(__FILE__).'/content');

By default, WordPress sets constant WP_CONSTANT_URL with value get_option('siteurl').'/wp-content'. Because we have changed the content directory from the default "wp-content" to "content", we must also set the new value for WP_CONSTANT_URL. To do this, we can’t reference function get_option since it hasn’t been defined yet, so we must either hardcode the domain or, possibly better, we can retrieve it from $_SERVER like this:

$s = empty($_SERVER["HTTPS"]) ? '' : ($_SERVER["HTTPS"] == "on") ? "s" : ""; $sp = strtolower($_SERVER["SERVER_PROTOCOL"]); $protocol = substr($sp, 0, strpos($sp, "/")) . $s; $port = ($_SERVER["SERVER_PORT"] == "80") ? "" : (":".$_SERVER["SERVER_PORT"]); define('WP_CONTENT_URL', $protocol."://".$_SERVER[’SERVER_NAME'].$port.'/content');

We can now access the site on the browser under domain.com/wp/, and proceed to install WordPress. Once the installation is complete, we log into the Dashboard and activate the theme and plugins.

Finally, because WordPress was installed under subdirectory wp, the URL will contain path “/wp” when accessing the site. Let’s remove that (not for the admin side though, which by being accessed under /wp/wp-admin/ adds an extra level of security to the site).

The documentation proposes two methods to do this: with or without URL change. I followed both of them, and found the without URL change a bit unsatisfying because it requires specifying the domain in the .htaccess file, thus mixing application code and configuration information together. Hence, I’ll describe the method with URL change.

First, head to “General Settings” which you’ll find under domain.com/wp/wp-admin/options-general.php and remove the “/wp” bit from the “Site Address (URL)” value and save. After doing so, the site will be momentarily broken: browsing the homepage will list the contents of the directory, and browsing a blog post will return a 404. However, don’t panic, this will be fixed in the next step.

Next, we copy the index.php file to the root folder, and edit this new file, adding “wp/” to the path of the required file, like this:

/** Loads the WordPress Environment and Template */ require( dirname( __FILE__ ) . '/wp/wp-blog-header.php' );

We are done! We can now access our site in the browser under domain.com:

WordPress site successfully installed through Composer (Large preview)

Even though it has downloaded the whole WordPress core codebase and several libraries, our project itself involves only six files from which only five need to be committed to Git:

  1. .gitignore
  2. composer.json
  3. composer.lock
    This file is generated automatically by Composer, containing the versions of all installed dependencies.
  4. index.php
    This file is created manually.
  5. .htaccess
    This file is automatically created by WordPress, so we could avoid committing it, however, we may soon customize it for the application, in which case it requires committing.

The remaining sixth file is wp-config.php which must not be committed since it contains environment information.

Not bad!

The process went pretty smoothly, however, it could be improved if the following issues are dealt better:

  1. Some application code is not committed under version control.
    Since it contains environment information, the wp-config.php file must not be committed to Git, instead requiring to maintain a different version of this file for each environment. However, we also added a line of code to load Composer’s autoloader in this file, which will need to be replicated for all versions of this file across all environments.
  2. The installation process is not fully automated.
    After installing the dependencies through Composer, we must still install WordPress through its standard procedure, log-in to the Dashboard and change the site URL to not contain “wp/”. Hence, the installation process is slightly fragmented, involving both a script and a human operator.

Let’s see next how Bedrock fares for the same task.

Managing The Whole WordPress Site Through Bedrock

Bedrock is a WordPress boilerplate with an improved folder structure, which looks like this:

├── composer.json ├── config │ ├── application.php │ └── environments │ ├── development.php │ ├── staging.php │ └── production.php ├── vendor └── web ├── app │ ├── mu-plugins │ ├── plugins │ ├── themes │ └── uploads ├── wp-config.php ├── index.php └── wp

The people behind Roots chose this folder structure in order to make WordPress embrace the Twelve Factor App, and they elaborate how this is accomplished through a series of blog posts. This folder structure can be considered an improvement over the standard WordPress one on the following accounts:

  • It adds support for Composer by moving WordPress’ core out of the root folder and into folder web/wp;
  • It enhances security, because the configuration files containing the database information are not stored within folder web, which is set as the web server’s document root (the security threat is that, if the web server goes down, there would be no protection to block access to the configuration files);
  • The folder wp-content has been renamed as “app”, which is a more standard name since it is used by other frameworks such as Symfony and Rails, and to better reflect the contents of this folder.

Bedrock also introduces different config files for different environments (development, staging, production), and it cleanly decouples the configuration information from code through library PHP dotenv, which loads environment variables from a .env file which looks like this:

DB_NAME=database_name DB_USER=database_user DB_PASSWORD=database_password # Optionally, you can use a data source name (DSN) # When using a DSN, you can remove the DB_NAME, DB_USER, DB_PASSWORD, and DB_HOST variables # DATABASE_URL=mysql://database_user:database_password@database_host:database_port/database_name # Optional variables # DB_HOST=localhost # DB_PREFIX=wp_ WP_ENV=development WP_HOME=http://example.com WP_SITEURL=${WP_HOME}/wp # Generate your keys here: https://roots.io/salts.html AUTH_KEY='generateme' SECURE_AUTH_KEY='generateme' LOGGED_IN_KEY='generateme' NONCE_KEY='generateme' AUTH_SALT='generateme' SECURE_AUTH_SALT='generateme' LOGGED_IN_SALT='generateme' NONCE_SALT='generateme'

Let’s proceed to install Bedrock, following their instructions. First create a project like this:

composer create-project "roots/bedrock"

This command will bootstrap the Bedrock project into a new folder “bedrock”, setting up the folder structure, installing all the initial dependencies, and creating an .env file in the root folder which must contain the site’s configuration. We must then edit the .env file to add the database configuration and secret keys and salts, as would normally be required in wp-config.php file, and also to indicate which is the environment (development, staging, production) and the site’s domain.

Next, we can already add themes and plugins. Bedrock comes with themes twentyten to twentynineteen shipped by default under folder web/wp/wp-content/themes, but when adding more themes through Composer these are installed under web/app/themes. This is not a problem, because WordPress can register more than one directory to store themes through function register_theme_directory.

Bedrock includes the WPackagist information in the composer.json file, so we can already install themes and plugins from this repository. To do so, simply step on the root folder of the project and execute the composer require command for each theme and plugin to install (this command already installs the dependency, so there is no need to execute composer update):

cd bedroot composer require "wpackagist-theme/zakra" composer require "wpackagist-plugin/akismet":"^4.1" composer require "wpackagist-plugin/bbpress":">=2.5.12"

The last step is to configure the web server, setting the document root to the full path for the web folder. After this is done, heading to domain.com in the browser we are happily greeted by WordPress installation screen. Once the installation is complete, we can access the WordPress admin under domain.com/wp/wp-admin and activate the installed theme and plugins, and the site is accessible under domain.com. Success!

Installing Bedrock was pretty smooth. In addition, Bedrock does a better job at not mixing the application code with environment information in the same file, so the issue concerning application code not being committed under version control that we got with the previous method doesn’t happen here.

Conclusion

With the launch of Gutenberg and the upcoming bumping up of PHP’s minimum required version, WordPress has entered an era of modernization which provides a wonderful opportunity to rethink how we build WordPress sites to make the most out of newer tools and technologies. Composer, Packagist, and WPackagist are such tools which can help us produce better WordPress code, with an emphasis on reusable components to produce modular applications which are easy to test and bugfix.

First released in 2012, Composer is not precisely what we would call “new” software, however, it has not been incorporated to WordPress’ core due to a few incompatibilities between WordPress’ architecture and Composer’s requirements. This issue has been an ongoing source of frustration for many members of the WordPress development community, who assert that the integration of Composer into WordPress will enhance creating and releasing software for WordPress. Fortunately, we don’t need to wait until this issue is resolved since several actors took the matter into their own hands to provide a solution.

In this article, we reviewed two projects which provide an integration between WordPress and Composer: manually setting our composer.json file depending on John P. Bloch’s mirror of WordPress’ core, and Bedrock by Roots. We saw how these two alternatives, which offer a different amount of freedom to shape the project’s folder structure, and which are more or less smooth during the installation process, can succeed at fulfilling our requirement of completely managing a WordPress site, including the installation of the core, themes, and plugins.

If you have any experience using WordPress and Composer together, either through any of the described two projects or any other one, I would love to see your opinion in the comments below.

I would like to thank Andrey “Rarst” Savchenko, who reviewed this article and provided invaluable feedback.

Further Reading on SmashingMag: (rb, ra, il)
Categories: Around The Web

Organizing Brainstorming Workshops: A Designer’s Guide

Smashing Magazine - Fri, 03/01/2019 - 10:00am
Organizing Brainstorming Workshops: A Designer’s Guide Organizing Brainstorming Workshops: A Designer’s Guide Slava Shestopalov 2019-03-01T15:00:28+01:00 2019-03-21T10:50:18+00:00

When you think about the word “brainstorming”, what do you imagine? Maybe a crowd of people who you used to call colleagues, outshouting each other, assaulting the whiteboard, and nearly throwing punches to win control over the projector? Fortunately, brainstorming has a bright side: It’s a civilized process of generating ideas together. At least this is how it appears in the books on creativity. So, can we make it real?

I have already tried the three methodologies presented in this article with friends of mine, so there is no theorizing. After reaching the end of this article, I hope that you’ll be able to organize brainstorming sessions with your colleagues and clients, and co-create something valuable. For instance, ideas about a new mobile application or a design conference agenda.

Building Diverse Design Teams

What is diversity and what does it have to do with design? It’s important to understand that design is not only critical to solving problems on the product and experience level, but also relevant on a bigger scale to close social divides and to create inclusive communities. Learn more →

Universal Principles

Don’t be surprised to notice all brainstorming techniques have much in common. Although “rituals” vary, the essence is the same. Participants look at the subject from different sides and come up with ideas. They write their thoughts down and then make sorting or prioritizing. I know, sounds easy as pie, doesn’t it? But here’s the thing. Without the rules of the game, brainstorming won’t work. It all boils down to just three crucial principles:

  1. The more, the better.
    Brainstorming aims at the quantity, which later turns into quality. The more ideas a team generates the wider choice it gains. It’s normal when two or more participants say the same thing. It’s normal if some ideas are funny. A facilitator’s task is encouraging people to share what is hidden in their mind.
  2. No criticism.
    The goal of brainstorming is to generate a pool of ideas. All ideas are welcome. A boss has no right to silence a subordinate. An analyst shouldn’t make fun of a colleague’s “fantastic” vision. A designer shouldn’t challenge the usability of a teammates’ suggestion.
  3. Follow the steps.
    Only a goal-oriented and time-bound activity is productive, whereas uncontrolled bursts of creativity, as a rule, fail. To make a miracle happen, organize the best conditions for it.

Here are the universal slides you can use as an introduction to any brainstorming technique.

(Large preview)

Now when the principles are clear, you are to decide who’s going to participate. The quick answer is diversity. Invite as many different experts as possible including business owners, analysts, marketers, developers, salespeople, potential or real users. All participants should be related to the subject or be interested in it. Otherwise, they’ll fantasize about the topic they’ve never dealt with and don’t want to.

One more thing before we proceed with the three techniques (Six Thinking Hats, Walt Disney’s Creative Strategy, and SCAMPER). When can a designer or other specialist use brainstorming? Here are two typical cases:

  1. There is a niche for a new product, service or feature but the team doesn’t have a concept of what it might be.
  2. An existing product or service is not as successful as expected. The team generally understands the reasons but has no ideas on how to fix it.
1. Six Thinking Hats

The first technique I’d like to present is known as “Six Thinking Hats”. It was invented in 1985 by Edward de Bono, a Maltese physician, psychologist, and consultant. Here’s a quick overview:

Complexity Normal Subject A process, a service, a product, a feature, anything. For example, one of the topics at our session was the improvement of the designers' office infrastructure. Another team brainstormed about how to improve the functionality of the Sketch app. Duration 1–1.5 hours Facilitation One facilitator for a group of 5–8 members. If there are more people, better divide them into smaller groups and involve assistants. We split our design crew of over 20 people into three workgroups, which were working simultaneously on their topics. Brainstorming workshop for the ELEKS design team (Large preview) Materials
  • Slides with step-by-step instructions.
  • A standalone timer or laptop with an online timer in the fullscreen mode.
  • 6 colored paper hats or any recognizable hat symbols for each participant. The colors are blue, yellow, green, white, red, and black. For example, we used crowns instead of hats, and it was fun.
  • Sticky notes of 6 colors: blue, yellow, green, white, red, and brown or any other dark tint for representing black. 1–2 packs of each color per team of 5–8 people would be enough.
  • A whiteboard or a flip-chart or a large sheet of paper on a table or wall.
  • Black marker pens for each participant (markers should be whiteboard-safe if you choose this kind of surface).
Process

Start a brainstorming session with a five-minute intro. What will participants do? Why is it important? What will the outcome be? What’s next? It’s time to explain the steps. In my case, we described the whole process beforehand to ensure people get the concept of “thinking hats.” De Bono’s “hat” represents a certain way of perceiving reality. Different people are used to “wearing” one favorite “hat” most of the time, which limits creativity and breeds stereotypes.

For example, risk analysts are used to finding weaknesses and threats. That’s why such a phenomenon as gut feeling usually doesn’t ring them a bell.

(Large preview)

Trying on “hats” is a metaphor that helps people to start thinking differently with ease. Below is an example of the slides that explain what each “hat” means. Our goal was to make people feel prepared, relaxed, and not afraid of the procedure complexity.

(Large preview)

The blue “hat” is an odd one out. It has an auxiliary role and embodies the process of brainstorming itself. It starts the session and finishes it. White, yellow, black, red, and green “hats” represent different ways to interpret reality.

For example, the red one symbolizes intuitive and emotional perception. When the black “hat” is on, participants wake up their inner “project manager” and look at the subject through the concepts of budgets, schedule, cost, and revenue.

There are various schemas of “hats” depending on the goal. We wanted to try all the “hats” and chose a universal, all-purpose order:

Blue Preparation White Collecting available and missing data Red Listening to emotions and unproven thoughts Yellow Noticing what is good right now Green Thinking about improvements and innovations Black Analyzing risks and resources Blue Summarizing (Large preview)

Now the exercise itself. Each slide is a cheat sheet with a task and prompts. When a new step starts and a proper slide appears on the screen, a facilitator starts the timer. Some steps have an extended duration; other steps require less time. For instance, it’s easy to agree on a topic formulation and draw a canvas but writing down ideas is a more time-consuming activity.

When participants see a “hat” slide (except the blue one), they are to generate ideas, write them on sticky notes and put the notes on the whiteboard, flip-chart or paper sheet. For example, the yellow “hat” is displayed on the screen. People put on yellow paper hats and think about the benefits and nice features the subject has now and why it may be useful or attractive. They concisely write these thoughts on the sticky notes of a corresponding color (for the black “hat” — any dark color can be used so that you don’t need to buy special white markers). All the sticky notes of the same color should be put in the corresponding column of the canvas.

(Large preview)

The last step doesn’t follow the original technique. We thought it would be pointless to stick dozens of colored notes and call it a day. We added the Affinity sorting part aimed at summarizing ideas and making the moment of their implementation a bit closer. The teams had to find notes about similar things, group them into clusters and give a name to each cluster.

For example, in the topic “Improvement of the designers’ office infrastructure,” my colleagues created such clusters as “Chair ergonomics,” “Floor and walls,” “Hardware upgrade.”

(Large preview)

We finished the session with the mini-presentations of findings. A representative from each team listed the clusters they came up with and shared the most exciting observation or impression.

Walt Disney’s Creative Strategy

Walt Disney’s creative method was discovered and modeled by Robert Dilts, a neuro-linguistic programming expert, in 1994. Here’s an overview:

Complexity Easy Subject Anything, especially projects you’ve been postponing for a long time or dreams you cannot start fulfilling for unknown reasons. For example, one of the topics I dealt with was “Improvement of the designer-client communication process.” Duration 1 hour Facilitation One facilitator for a group of 5–8 members. When we conducted an educational workshop on brainstorming, my co-trainers and I had four teams of six members working simultaneously in the room. Educational session on brainstorming for the Projector Design School (Large preview) Materials
  • Slides with step-by-step instructions.
  • A standalone timer or laptop with an online timer in the fullscreen mode.
  • Standard or large yellow sticky notes (1–2 packs per team of 5–8 people).
  • Small red sticky notes (1–2 packs per team).
  • The tiniest sticky stripes or sticky dots for voting (1 pack per team).
  • A whiteboard or a flip-chart or a large sheet of paper on a table or wall.
  • Black marker pens for each participant (markers should be whiteboard-safe if you choose this kind of surface).
Process

This technique is called after the original thinking manner of Walt Disney, a famous animator and film producer. Disney didn’t use any “technique”; his creative process was intuitive yet productive. Robert Dilts, a neuro-linguistic programming expert, discovered this creative knowhow much later based on the memories of Disney’s colleagues. Although original Dilts’s concept is designed for personal use, we managed to turn it into a group format.

(Large preview)

Disney’s strategy works owing to the strict separation of three roles — the dreamer, the realist, and the critic. People are used to mixing these roles while thinking about the future, and that’s why they often fail. “Let’s do X. But it’s so expensive. And risky… Maybe later,” this is how an average person dreams. As a result, innovative ideas get buried in doubts and fears.

In this kind of brainstorming, the facilitator’s goal is to prevent participants from mixing the roles and nipping creative ideas in the bud. We helped the team to get into the mood and extract pure roles through open questions on the slides and introductory explanations.

(Large preview)

For example, here is my intro to the first role:

“The dreamer is not restrained by limitations or rules of the real world. The dreamer generates as many ideas as possible and doesn’t think about the obstacles on the way of their implementation. S/he imagines the most fun, easy, simple, and pleasant ways of solving a problem. The dreamer is unaware of criticism, planning, and rationalism altogether.”

As a result, participants should have a bunch of encircled ideas.

When participants come up with the cloud of ideas, they proceed to the next step. It’s important to explain to them what the second role means. I started with the following words:

“The realist is the dreamer’s best friend. The realist is the manager who can convert a vague idea into a step-by-step plan and find necessary resources. The realist has no idea about criticism. He or she tries to find some real-world implementation for dreamer’s ideas, namely who, when, and how can make an idea true.”

Brainstormers write down possible solutions on sticky notes and put them on the corresponding idea circles. Of course, some of the ideas can have no solution, whereas others may be achieved in many ways.

(Large preview)

The third role is the trickiest one because people tend to think this is the guy who drags dreamer’s and realist’s work through the mud. Fortunately, this is not true.

I started my explanation:

“The critic is the dreamer’s and realist’s best friend. This person analyses risks and cares about the safety of proposed solutions. The critic doesn’t touch bare ideas but works with solutions only. The critic’s goal is to help and foresee potential issues in advance.”

The team defines risks and writes them down on smaller red notes. A solution can have no risks or several risks.

After that’s done, team members start voting for the ideas they consider worth further working on. They make a decision based on the value of an idea, the availability of solutions, and the severity of connected risks. Ideas without solutions couldn’t be voted for since they had no connection with reality.

During my workshops, each participant had three voting dots. They could distribute them in different ways, e.g. by sticking the dots to three different ideas or supporting one favorite idea with all of the dots they had.

(Large preview)

The final activity is roadmapping. The team takes the ideas that gained the most support (typically, 6–10) and arrange them on a timeline depending on the implementation effort. If an idea is easy to put into practice, it goes to the column “Now.” If an idea is complex and requires a lot of preparation or favorable conditions, it’s farther on the timeline.

Of course, there should be time for sharing the main findings. Teams present their timelines with shortlisted ideas and tell about the tendencies they have observed during the exercise.

SCAMPER

This technique was proposed in 1953 by Alex Osborn, best known for co-founding and leading BBDO, a worldwide advertising agency network. A quick overview:

Complexity Normal to difficult Subject Ideally, technical or tangible things, although the author and evangelists of this method say it’s applicable for anything. From my experience, SCAMPER works less effective with abstract objects. For example, the team barely coped with the topic “Improve communication between a designer and client,” but it worked great for “Invent the best application for digital prototyping.” Duration Up to 2 hours Facilitation One facilitator for a group of 5–8 members Brainstorming workshop for the ELEKS design team (Large preview) Materials
  • Slides with step-by-step instructions.
  • A standalone timer or laptop with an online timer in the fullscreen mode.
  • Standard yellow sticky notes (7 packs per team of 5–8 people).
  • A whiteboard or a flip-chart or a large sheet of paper on a table or wall.
  • Black marker pens for each participant (markers should be whiteboard-safe if you choose this kind of surface).
  • Optionally: Thinkpak cards by Michael Michalko (1 pack per team).
Process

This brainstorming method employs various ways to modify an object. It’s aimed at activating the inventory thinking and helps to optimize an existing product or create a brand new thing.

(Large preview)

Each letter in the acronym represents a certain transformation you can apply to the subject of brainstorming.

S  Substitute C Combine A Adapt M Modify P Put to other uses E Eliminate R Rearrange/Reverse

It’s necessary to illustrate each step with an example and ask participants to generate a couple of ideas themselves for the sake of training. As a result, you’ll be sure they won’t get stuck.

We explained the mechanism by giving sample ideas for improving such an ordinary object as a ballpoint pen.

  • Substitute the ink with something edible.
  • Combine the body and the grip so that they are one piece.
  • Adapt a knife for “writing” on wood like a pen.
  • Modify the body so that it becomes flexible — for wearing as a bracelet.
  • Use a pen as a hairpin or arrow for darts.
  • Eliminate the clip and use a magnet instead.
  • Reverse the clip. As a result, the nib will be oriented up, and the pen won’t spill in a pocket.
(Large preview)

After the audience doesn’t have questions left, you can start. First of all, team members agree on the subject formulation. Then they draw a canvas on a whiteboard or large paper sheet.

Once a team sees one of the SCAMPER letters on the screen, they start generating ideas using the corresponding method: substitute, combine, adapt, modify, and so on. They write the ideas down and stick the notes into corresponding canvas columns.

The questions on the slides remind what each step means and help to get in a creative mood. Time limitation helps to concentrate and not to dive into discussions.

(Large preview)

Affinity sorting — the last step — is our designers’ contribution to the original technique. It pushes the team to start implementation. Otherwise, people quickly forget all valuable findings and return to the usual state of things. Just imagine how discouraging it will be if the results of a two-hour ideation session are put on the back burner.

(Large preview) Thinkpak Cards

It’s a set of brainstorming cards created by Michael Michalko. Thinkpak makes a session more exciting through gamification. Each card represents a certain letter from SCAMPER. Participants shuffle the pack, take cards in turn and come up with corresponding ideas about an object. It’s fun to compete in the number of ideas each participant generates for a given card within a limited time, for instance, three or five minutes.

My friends and I have tried brainstorming both with and without a Thinkpak; it works both ways. Cards are great for training inventory thinking. If your team has never participated in brainstorming sessions, it’ll be great to play the cards first and then switch to a business subject.

Lessons Learned
  1. Dry run.
    People often become disappointed in brainstorming if the first session they participate in fails. Some people I worked with have a prejudice towards creativity and consider it the waste of time or something not proven scientifically. Fortunately, we tried all the techniques internally — in the design team. As a result, all the actual brainstorming sessions went well. Moreover, our confidence helped others to believe in the power of brainstorming exercises.
  2. Relevant topic and audience.
    Brainstorming can fail if you invite people who don’t have a relevant background or the power and willing to change anything. Once I asked a team of design juniors to ideate about improving the process of selling design services to clients. They lacked the experience and couldn’t generate plenty of ideas. Fortunately, it was a training session, and we easily changed the topic.
  3. Documenting outcomes.
    So, the session is over. Participants go home or return to their workplaces. Almost surely the next morning they will recall not a single thing. I recommend creating a wrap-up document with photos and digitized canvases. The quicker you write and share it, the higher the chances will be that the ideas are actually implemented.
Further Resources (dm, ra, il)
Categories: Around The Web

Fresh Spring Vibes For Your Desktop (March 2019 Wallpapers Edition)

Smashing Magazine - Thu, 02/28/2019 - 5:05am
Fresh Spring Vibes For Your Desktop (March 2019 Wallpapers Edition) Fresh Spring Vibes For Your Desktop (March 2019 Wallpapers Edition) Cosima Mielke 2019-02-28T10:05:48+01:00 2019-03-20T11:43:54+00:00

Spring is coming! With March just around the corner, nature is slowly but surely awakening from its winter sleep. And, well, even if spring seems far away in your part of the world, this month’s wallpaper selection is bound to at least get your ideas springing.

Just like every month since more than nine years already, artists and designers from across the globe got out their favorite tools and designed unique wallpapers to cater for some fresh inspiration on your desktop and mobile screens. The wallpapers come in versions with and without a calendar for March 2019 and can be downloaded for free. A big thank-you to everyone who submitted their designs! As a little bonus goodie, we also added some favorites from past years’ March editions at the end of this post. Now which one will make it to your screen?

Please note that:

  • All images can be clicked on and lead to the preview of the wallpaper,
  • You can feature your work in our magazine by taking part in our Desktop Wallpaper Calendar series. We are regularly looking for creative designers and artists to be featured on Smashing Magazine. Are you one of them?
Further Reading on SmashingMag: Time To Wake Up

“Rays of sunlight had cracked into the bear’s cave. He slowly opened one eye and caught a glimpse of nature in blossom. Is it spring already? Oh, but he is so sleepy. He doesn’t want to wake up, not just yet. So he continues dreaming about those sweet sluggish days while everything around him is blooming.” — Designed by PopArt Studio from Serbia.

Queen Bee

“Spring is coming! Birds are singing, flowers are blooming, bees are flying… Enjoy this month!” — Designed by Melissa Bogemans from Belgium.

Bunny O’Hare

“When I think of March I immediately think of St. Patrick’s Day and my Irish heritage... and then my head fills with pub music! I had fun putting a twist on this month’s calendar starring my pet rabbit. Erin go Braugh.” — Designed by Heather Ozee from the United States.

A Bite Of Spring

Designed by Ricardo Gimenes from Sweden.

Balance

“International Women’s Day on March 8th is the inspiration behind this artwork. Through this artwork, we wish a jovial, strong and successful year ahead for all women around the world.” — Designed by Sweans Technologies from London.

Stunning Beauty

“A recent vacation to the Philippines led me to Palawan, specifically El Nido, where I was in awe of the sunset. I wanted to emphasize the year in the typography as a reminder that, even though we are three months in, our resolutions are still fresh and new and waiting for us to exceed them! Photograph shot by @chrishernando, whose companionship and permission I am so grateful for.” — Designed by Mary Walker from the United States.

Spring Time!

“Spring is here! Giraffes are starting to eat the green leaves.” — Designed by Veronica Valenzuela from Spain.

Banished

“The legend of St. Patrick banishing snakes from Ireland.” — Designed by Caitey Kennedy from the United States.

Oldies But Goodies

In more than nine years running this community project, a lot of wallpaper gems have accumulated in our archives. Let’s take a look back and rediscover some March favorites from past years. Please note that these wallpapers don’t come with a calendar.

Let’s Get Outside

“Let’s get outside and seize the beginning of Spring. Who knows what adventures might await us there?” — Designed by Lívia Lénárt from Hungary.

The Unknown

“I made a connection, between the dark side and the unknown lighted and catchy area.” — Designed by Valentin Keleti from Romania.

Imagine

Designed by Romana Águia Soares from Portugal.

Spring Bird

Designed by Nathalie Ouederni from France.

Ballet

“A day, even a whole month aren’t enough to show how much a woman should be appreciated. Dear ladies, any day or month are yours if you decide so.” — Designed by Ana Masnikosa from Belgrade, Serbia.

Wake Up!

“Early spring in March is for me the time when the snow melts, everything isn’t very colorful. This is what I wanted to show. Everything comes to life slowly, as this bear. Flowers are banal, so instead of a purple crocus we have a purple bird-harbinger.” — Designed by Marek Kedzierski from Poland.

Spring Is Coming!

“Spring is the best part of the year! Nature breaking free and spring awakening is symbolic of our awakening.” — Designed by Silvia Bukovac from Croatia.

Spring Is Inevitable!

“Spring is round the corner. And very soon plants will grow on some other planets too. Let’s be happy about a new cycle of life.” — Designed by Igor Izhik from Canada.

Tune In To Spring!

Designed by Iquadart from Belarus.

Wake Up!

“I am the kind of person that prefers cold but I do love spring since it’s the magical time when flowers and trees come back to life and fill the landscape with beautiful colors.” — Designed by Maria Keller from Mexico.

Let’s Spring!

“After some freezing months, it’s time to enjoy the sun and flowers. It’s party time, colours are coming, so let’s spring!” — Designed by Colorsfera from Spain.

MARCHing Forward!

“If all you want is a little orange dinosaur MARCHing (okay, I think you get the pun) across your monitor, this wallpaper was made just for you! This little guy is my design buddy at the office and sits by (and sometimes on top of) my monitor. This is what happens when you have designer’s block and a DSLR.” — Designed by Paul Bupe Jr from Statesboro, GA.

Waiting For Spring

“As days are getting longer again and the first few flowers start to bloom, we are all waiting for Spring to finally arrive.” Designed by Naioo from Germany.

March Fusion

Designed by Rio Creativo from Poland.

Daydream

“A daydream is a visionary fantasy, especially one of happy, pleasant thoughts, hopes or ambitions, imagined as coming to pass, and experienced while awake.” Designed by Bruna Suligoj from Croatia.

Sweet March

“Digital collage, based on past and coming spring. The idea is to make it eternal or at least make it eternal in our computers! Hope you like it.” Designed by Soledad Martelletti from Argentina.

Knowledge

“Exploring new worlds is much like exploring your own mind, creativity and knowledge. The only way to learn what’s really inside you is by trying something new. The illustration is my very own vision of the knowledge. It’s placed in some mysterious habitat. It’s a space where people learn from each other, find new talents and study their own limits.” — Designed by Julia Wójcik from Poland.

Join In Next Month!

Please note that we respect and carefully consider the ideas and motivation behind each and every artist’s work. This is why we give all artists the full freedom to explore their creativity and express emotions and experience throughout their works. This is also why the themes of the wallpapers weren’t anyhow influenced by us but rather designed from scratch by the artists themselves.

Thank you to all designers for their participation. Join in next month!

Categories: Around The Web

Breaking Boxes With CSS Fragmentation

Smashing Magazine - Wed, 02/27/2019 - 9:00am
Breaking Boxes With CSS Fragmentation Breaking Boxes With CSS Fragmentation Rachel Andrew 2019-02-27T14:00:00+01:00 2019-03-18T23:05:35+00:00

In this article, I’m going to introduce you to the CSS Fragmentation specification. You might never have heard of it, however, if you have ever created a print stylesheet and wanted to control where the content breaks between pages, or multi-column layout and wanted to stop a figure breaking between columns, you have encountered it.

I find that quite often problems people report with multicol are really problems with browser support of fragmentation. After a quick rundown of the properties contained in this specification, I’ll be explaining the current state of browser support and some of the things you can do to get it working as well as it can in your multicol and print projects.

What Is Fragmentation?

Fragmentation in CSS describes the process by which content becomes broken up into different boxes. Currently, we have two places in which we might run into fragmentation on the web: when we print a document, and if we use multi-column layout. These two things are essentially the same. When you print (or save to PDF) a webpage, the content is fragmented into as many pages as are required to print your content.

When you use multicol, the content is fragmented into columns. Each column box is like a page in the paged context. If you think of a set of columns as being much like a set of pages it can be a helpful way to think about multicol and how fragmentation works in it.

If you take a look at the CSS Fragmentation Specification you will see a third fragmented context mentioned — that context is Regions. As there are no current usable implementations of Regions, we won’t be dealing with that in this article, but instead looking at the two contexts that you might come across in your work.

Block And Inline Boxes

I am going to mention block boxes a lot in this article. Every element of your page has a box. Some of those boxes are laid out as blocks: paragraphs, list items, headings. These are said to be participating in a block formatting context. Others are inline such as the words in a paragraph, spans and anchor elements. These participate in an inline formatting context. Put simply, when I refer to a block box, I’m talking about boxes around things like paragraphs. When dealing with fragmentation, it is important to know which kind of box you are dealing with.

For more information on block and inline layout, see the MDN article “Block And Inline Layout In Normal Flow”. It is one of those things that we probably all understand on some level but might not have encountered the terminology of before.

Controlling Breaks

Whether you are creating a print stylesheet, using a specific print user agent to make a PDF,or using multicol, you will sometimes run into problems that look like this.

In the below multicol example, I have some content which I am displaying as three columns. In the middle of the content is a boxed out area, which is being broken across two columns. I don’t want this behavior — I would like the box to stay together.

The box breaks across two columns (Large preview)

To fix this, I add the property break-inside: avoid to the box. The break-inside property controls breaks inside elements when they are in a fragmented context. In a browser which supports this property, the box will now stay in one of the columns. The columns will look less well balanced, however, that is generally a better thing than ending up with the boxout split across columns.

See the Pen Simple break-inside example by (Rachel Andrew.

The break-inside property is one of the properties detailed in the fragmentation spec. The full list of properties is as follows:

  • break-before
  • break-after
  • break-inside
  • orphans
  • widows
  • box-decoration-break

Let’s have a look at how these are supposed to work before we move onto what actually happens in browsers.

The break-before And break-after Properties

There are two properties that control breaks between block-level boxes: break-before and break-after. If you have an h2 followed by two paragraphs <p> you have three block boxes and you would use these properties to control the breaks between the heading and first paragraph, or between the two paragraphs.

The properties are used on selectors which target the element you want to break before or after.

For example, you might want your print stylesheet to break onto a new page every time there is a level 2 heading. In this case, you would use break-before: page on the h2 element. This controls the fragmentation and ensures there is always a break before the box of the h2 element.

h2 { break-before: page; }

Another common requirement is to prevent headings ending up as the last thing on a page or column. In this case, you might use break-after with a value of avoid. This should prevent a break directly after the box of the element:

h1, h2, h3, h4 { break-after: avoid; } Fragments Within Fragments

It is possible that you might have an element that is fragmented nested inside another. For example, having a multicol inside something which is paged. In that case, you might want to control breaks for pages but not for columns, or the other way around. This is why we have values such as page which would always force a break before or after the element but only when the fragment is a page. Or avoid-page which would avoid a break before or after the element only for paged contexts.

The same applies to columns. If you use the value column, this would always force a break before or after that element, but only for multicol contexts. The value avoid-column would prevent a break in multicol contexts.

There is an always value in the Level 4 specification which indicates that you want to break through everything — page or column. However, as a recent addition to the spec it is not currently useful to us.

Additional Values For Paged Media

If you are creating a book or magazine, you have left and right pages. You might want to control breaking in order to force something onto the left or right page of a spread. Therefore, using the following would insert one or two-page breaks before the h2 to ensure it was formatted as a right page.

h2 { break-before: right; }

There are also recto and verso values which relate to page progression as books written in a vertical or right to left language have a different page progression than books written in English. I’m not going to cover these values further in this article as I’m primarily concerned with what is possible from the browser this time.

break-inside

We have already seen an example of the break-inside property. This property controls breaking inside block boxes, e.g. inside a paragraph, heading or a div.

Things that you may not want to break can include a boxout as described above: figures where you do not want the caption detached from the image, tables, lists and so on. Add break-inside: avoid to any container you don’t wish to break in any fragmentation context. If you only wish to avoid breaks between columns use break-inside: avoid-column and between pages break-inside: avoid-page.

The orphans And widows Properties

The orphans and widows properties deal with how many lines should be left before or after a break (either caused by a column or a new page). For example, if I want to avoid a single line being left at the end of a column, I would use the orphans property, as in typography, an orphan is the first line of a paragraph that appears alone at the bottom of a page with the rest of the paragraph broken onto another page. The property should be added to the same element which is fragmenting (in our case, the multicol container).

.container { column-count: 3; orphans: 2; }

To control how many lines should be at the top of a column or page after a break, use widows:

.container { column-count: 3; widows: 2; }

These properties deal with breaks between inline boxes such as the lines of words inside a paragraph. Therefore, they don’t help in the situation where a heading or other block element is alone at the bottom of a column or page, you need the break properties discussed above for that.

Box Decoration

A final property that may be of interest is the box-decoration-break property. This controls the situation where you have a box with a border broken between two column boxes or pages. Do you want the border to essentially be sliced in half? Or do you want each of the two halves of the box to be wrapped fully in a border?

The first scenario is the default, and is as if you set the box-decoration-break property to slice on the box.

.box { box-decoration-break: slice; } A value of slice means the border is effectively sliced in half (Large preview)

To get the second behavior, set box-decoration-break to clone.

.box { box-decoration-break: clone; } A value of clone means the border is wrapped fully round each fragment of the box (Large preview) Browser Support For Fragmentation

Now we come to the reason I don’t have a bunch of CodePen examples above to demo all of this to you, and the main reason for my writing this article. Browser support for these properties is not great.

If you are working in Paged Media with a specific user agent such as Prince, then you can enjoy really good support for fragmentation, and will probably find these properties very useful. If you are working with a web browser, either in multicol, creating print stylesheets, or using something like Headless Chrome to generate PDFs, support is somewhat patchy. You’ll find that the browser with the best support is Edge — until it moves to Chromium anyway!

Can I Use isn’t overly helpful with explaining support due to mixing the fragmentation properties in with multicol, then having some separate data for legacy properties. So, as part of the work I’ve been doing for MDN to document the properties and their support, I began testing the actual browser support. What follows is some advice based on that testing.

Legacy And Vendor Prefixed Properties

I can’t go much further without a history lesson. If you find you really need support for fragmentation then you may find some relief in the legacy properties which were originally part of CSS2 (or in some prefixed properties that exist).

In CSS2, there were properties to control page breaking. Multicol didn’t exist at that point, so the only fragmented context was a paged one. This meant that three specific page breaking properties were introduced:

  • page-break-before
  • page-break-after
  • page-break-inside

These work in a similar way to the more generic properties without the page- prefix, controlling breaks before, after and inside boxes. For print stylesheets, you will find that some older browsers which do not support the new break- properties, do support these page prefixed properties. The properties are being treated as aliases for the new properties.

In a 2005 Working Draft of the multicol specification are details of breaking properties for multicol — using properties prefixed with column- (i.e. column-break-before, column-break-after, and column-break-inside). By 2009, these had gone, and a draft was in the multicol specification for unprefixed break properties which eventually made their way into the CSS Fragmentation specification.

However, some vendor prefixed column-specific properties were implemented based on these properties. These are:

  • -webkit-column-break-before
  • -webkit-column-break-after
  • -webkit-column-break-inside
Support For Fragmentation In Multicol

The following is based on testing these features in multicol contexts. I’ve tried to explain what is possible, but do take a look at the CodePens in whichever browsers you have available.

Multicol And break-inside

Support in multicol is best for the break-inside property. Up to date versions of Chrome, Firefox, Edge, and Safari all support break-inside: avoid. So you should find that you can prevent boxes from breaking between columns when using multicol.

Several browsers, with the exception of Firefox, support the -webkit-column-break-inside property, this can be used with a value of avoid and may prevent boxes breaking between columns which do not have support for break-inside.

Firefox supports page-break-inside: avoid in multicol. Therefore, using this property will prevent breaks inside boxes in Firefox browsers prior to Firefox 65.

This means that if you want to prevent breaks between boxes in multicol, using the following CSS will cover as many browsers as possible, going back as far as possible.

.box { -webkit-column-break-inside: avoid; page-break-inside: avoid; break-inside: avoid; }

As for the column value, explicitly stating that you only want to avoid breaks between columns, and not pages, works in all browsers except Firefox.

The below CodePen rounds up some of these tests in multicol so you can try them for yourself.

See the Pen Multicol Fragmentation Test: break-inside by Rachel Andrew.

Multicol And break-before

In order to prevent breaks before an element, you should be able to use break-before: avoid or break-before: avoid-column. The avoid property has no browser support.

Edge supports break-before: column to always force a break before the box of the element.

Safari, Chrome and Edge also support -webkit-column-break-before: always which will force a break before the box of the element. Therefore, if you want to force a break before the box of an element, you should use:

.box { -webkit-column-break-before: always; break-before: column; }

Preventing a break before the box is currently an impossible task. You can play around with some examples of these properties below:

See the Pen Multicol Fragmentation Test: break-before by Rachel Andrew).

Multicol And break-after

To prevent breaks after an element, to avoid it becoming the last thing at the bottom of a column, you should be able to use break-after: avoid and break-after: avoid-column. The only browser with support for these is Edge.

Edge also supports forcing breaks after an element by using break-after: column, Chrome supports break-after: column and also -webkit-column-break-after: always.

Firefox does not support break-after or any of the prefixed properties to force or allow breaks after a box.

Therefore, other than Edge, you cannot really avoid breaks after a box. If you want to force them, you will get results in some browsers by using the following CSS:

.box { -webkit-break-after: always; break-after: column; }

See the Pen Multicol Fragmentation Test: break-after by Rachel Andrew).

Support When Printing From The Browser

Whether you print directly from your desktop browser or generate PDF files using headless Chrome or some other solution reliant on browser technology doesn’t make any difference. You are reliant on the browser support for the fragmentation properties.

If you create a print stylesheet, you will find similar support for the break properties as for multicol; however, to support older browsers you should double up the properties to use the page- prefixed properties.

Print Stylesheets And break-inside

In modern browsers ,the break-inside property can be used to prevent breaks inside boxes, add the page-break-inside property to add support for older browsers.

.box { page-break-inside: avoid; break-inside: avoid; } Print Stylesheets And break-before

To force breaks before a box use break-before:page along with page-break-before: always.

.box { page-break-before: always; break-before: page; }

To avoid breaks before a box use break-before: avoid-page along with page-break-before: avoid.

.box { page-break-before: avoid; break-before: avoid-page; }

There is better support for the page and avoid-page values than we see for the equivalent multicol values. The majority of modern browsers have support.

Print Stylesheets And break-before

To force breaks after a box, use break-after: page along with page-break-after: always.

.box { page-break-after: always; break-after: page; }

To prevent breaks after a box use break-after: avoid-page along with page-break-after: avoid.

.box { page-break-after: avoid; break-after: avoid-page; } Widows And Orphans

The widows and orphans properties enjoy good cross-browser support — the only browser without an implementation being Firefox. I would suggest using these when creating a multicol layout or print stylesheet. If they don’t work for some reason, you will get widows and orphans, which isn’t ideal but also isn’t a disaster. If they do work your typography will look all the better for it.

box-decoration-break

The final property of box-decoration-break has support for multicol and print in Firefox. Safari, Chrome and other Chromium-based browsers support -webkit-box-decoration-break, but only on inline elements. So you can clone borders round lines of a sentence for example; they do not have support in the context we are looking at.

In the CodePen below, you can see that testing for -webkit-box-decoration-break: clone with Feature Queries returns true; however, the property has no effect on the border of the box in the multicol context.

See the Pen Multicol: box-decoration-break by Rachel Andrew.

Using Fragmentation

As you can see, the current state of fragmentation in browsers is somewhat fragmented! That said, there is a reasonable amount you can achieve and where it fails, the result tends to be suboptimal but not a disaster. Which means it is worth trying.

It is worth noting that being too heavy handed with these properties could result in something other than what you hoped for. If you are working on the web rather than print and force column breaks after every paragraph, then end up with more paragraphs than space for columns, multicol will end up overflowing in the inline direction. It will run out of columns to place your additional paragraphs. Therefore, even where there is support, you still need to test carefully, and remember that less is more in a lot of cases.

More Resources

To read more about the properties head over to MDN, I’ve recently updated the pages there and am also trying to keep the browser compat data up to date. The main page for CSS Fragmentation links to the individual property pages which have further examples, browser compat data and other information about using these properties.

(il)
Categories: Around The Web

Sliding In And Out Of Vue.js

Smashing Magazine - Tue, 02/26/2019 - 7:00am
Sliding In And Out Of Vue.js Sliding In And Out Of Vue.js Kevin Ball 2019-02-26T12:00:34+01:00 2019-03-18T10:35:21+00:00

Vue.js has achieved phenomenal adoption growth over the last few years. It has gone from a barely known open-source library to the second most popular front-end framework (behind only React.js).

One of the biggest reasons for its growth is that Vue is a progressive framework — it allows you to adopt bits and pieces at a time. Don’t need a full single page application? Just embed a component. Don’t want to use a build system? Just drop in a script tag, and you’re up and running.

This progressive nature has made it very easy to begin adopting Vue.js piecemeal, without having to do a big architecture rewrite. However, one thing that is often overlooked is that it’s not just easy to embed Vue.js into sites written with other frameworks, it’s also easy to embed other code inside of Vue.js. While Vue likes to control the DOM, it has lots of escape hatches available to allow for non-Vue JavaScript that also touches the DOM.

This article will explore the different types of third-party JavaScript that you might want to use, what situations you might want to use them inside of a Vue project, and then cover the tools and techniques that work best for embedding each type within Vue. We’ll close with some considerations of the drawbacks of these approaches, and what to consider when deciding if to use them.

This article assumes some familiarity with Vue.js, and the concepts of components and directives. If you are looking for an introduction to Vue and these concepts, you might check out Sarah Drasner’s excellent introduction to Vue.js series or the official Vue Guide.

Types Of Third-Party JavaScript

There are three major types of third-party JavaScript that we’ll look at in order of complexity:

  1. Non-DOM Touching Libraries
  2. Element Augmentation Libraries
  3. Components And Component Libraries
Non-DOM Libraries

The first category of third-party JavaScript is libraries that provide logic in the abstract and have no direct access to the DOM. Tools like moment.js for handling dates or lodash for adding functional programming utilities fall into this category.

These libraries are trivial to integrate into Vue applications, but can be wrapped up in a couple of ways for particularly ergonomic access. These are very commonly used to provide utility functionality, the same as they would in any other type of JavaScript project.

Element Augmentation Libraries

Element augmentation is a time-honored way to add just a bit of functionality to an element. Examples include tasks like lazy-loading images with lozad or adding input masking using Vanilla Masker.

These libraries typically impact a single element at a time, and expect a constrained amount of access to the DOM. They will likely be manipulating that single element, but not adding new elements to the DOM.

These tools typically are tightly scoped in purpose, and relatively straightforward to swap out with other solutions. They’ll often get pulled into a Vue project to avoid re-inventing the wheel.

Components And Component Libraries

These are the big, intensive frameworks and tools like Datatables.net or ZURB Foundation. They create a full-on interactive component, typically with multiple interacting elements.

They are either directly injecting these elements into the DOM or expect a high level of control over the DOM. They were often built with another framework or toolset (both of these examples build their JavaScript on top of jQuery).

These tools provide extensive functionality and can be challenging to replace with a different tool without extensive modifications, so a solution for embedding them within Vue can be key to migrating a large application.

How To Use In Vue Non-DOM Libraries

Integrating a library that doesn’t touch the DOM into a Vue.js project is relatively trivial. If you’re using JavaScript modules, simply importor require the module as you would in another project. For example:

import moment from 'moment'; Vue.component('my-component', { //… methods: { formatWithMoment(time, formatString) { return moment(time).format(formatString); }, });

If using global JavaScript, include the script for the library before your Vue project:

<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.22/vue.min.js"></script> <script src="/project.js"></script>

One additional common way to layer on a bit more integration is to wrap up your library or functions from the library using a filter or method to make it easy to access from inside your templates.

Vue Filters

Vue Filters are a pattern that allows you to apply text formatting directly inline in a template. Drawing an example from the documentation, you could create a ‘capitalize’ filter and then apply it in your template as follows:

{{myString | capitalize}}

When importing libraries having to do with formatting, you may want to wrap them up as a filter for ease of use. For example, if we are using moment to format all or many of our dates to relative time, we might create a relativeTime filter.

const relativeTime = function(value) { if (!value) return ''; return moment(value).fromNow(); }

We can then add it globally to all Vue instances and components with the Vue.filter method:

Vue.filter(’relativeTime', relativeTime);

Or add it to a particular component using the filters option:

const myComponent = { filters: { ’relativeTime': relativeTime, } }

You can play with this on CodePen here:

See the Pen Vue integrations: Moment Relative Value Filter by Kevin Ball.

Element Augmentation Libraries

Element augmentation libraries are slightly more complex to integrate than libraries that don’t touch the DOM — if you’re not careful, Vue and the library can end up at cross purposes, fighting each other for control.

To avoid this, you need to hook the library into Vue’s lifecycle, so it runs after Vue is done manipulating the DOM element, and properly handles updates that Vue instigates.

This could be done in a component, but since these libraries typically touch only a single element at a time, a more flexible approach is to wrap them in a custom directive.

Vue Directives

Vue directives are modifiers that can be used to add behavior to elements in your page. Vue ships with a number of built-in directives that you are likely already comfortable with — things like v-on, v-model, and v-bind. It is also possible to create custom directives that add any sort of behavior to an element — exactly what we’re trying to achieve.

Defining a custom directive is much like defining a component; you create an object with a set of methods corresponding to particular lifecycle hooks, and then add it to Vue either globally by running:

Vue.directive('custom-directive', customDirective);

Or locally in a component by adding it to the directives object in the component:

const myComponent = { directives: { 'custom-directive': customDirective, } } Vue Directive Hooks

Vue directives have the following hooks available to define behavior. While you can use all of them in a single directive, it is also not uncommon to only need one or two. They are all optional, so use only what you need.

  • bind(el, binding, vnode)
    Called once and only once, when the directive is first bound to an element. This is a good place for one-time setup work, but be cautious, i.e. the element exists, may not yet actually be in the document.
  • inserted(el, binding, vnode)
    Called when the bound element has been inserted into its parent node. This also does not guarantee presence in the document, but does mean if you need to reference the parent you can.
  • update(el, binding, vnode, oldVnode)
    Called whenever the containing component’s VNode has updated. There are no guarantees that other children of the component will have updated, and the value for the directive may or may not have changed. (You can compare binding.value to binding.oldValue to see and optimize away any unnecessary updates.)
  • componentUpdated(el, binding, vnode, oldVnode)
    Similar to update, but called after all children of the containing component have updated. If the behavior of your directive depends on its peers (e.g. v-else), you would use this hook instead of update.
  • unbind(el, binding, vnode)
    Similar to bind, this is called once and only once, when the directive is unbound from an element. This is a good location for any teardown code.

The arguments to these functions are:

  • el: The element the directive is bound to;
  • binding: An object containing information about the arguments and value of the directive;
  • vnode: The virtual node for this element produced by Vue’s compiler;
  • oldVNode: The previous virtual node, only passed to update and componentUpdated.

More information on these can be found in the Vue Guide on custom directives.

Wrapping The Lozad Library In A Custom Directive

Let’s look at an example of doing this type of wrapping using lozad, a lazy-loading library built using the Intersection Observer API. The API for using lozad is simple: use data-src instead of src on images, and then pass a selector or an element to lozad() and call observe on the object that is returned:

const el = document.querySelector('img'); const observer = lozad(el); observer.observe();

We can do this simply inside of a directive using the bind hook.

const lozadDirective = { bind(el, binding) { el.setAttribute('data-src', binding.value) ; let observer = lozad(el); observer.observe(); } } Vue.directive('lozad', lozadDirective)

With this in place, we can change images to lazy load by simply passing the source as a string into the v-lozad directive:

<img v-lozad="'https://placekitten.com/100/100'" />

You can observe this at work in this CodePen:

See the Pen Vue integrations: Lozad Directive Just Bind by Kevin Ball).

We’re not quite done yet though! While this works for an initial load, what happens if the value of the source is dynamic, and Vue changes it? This can be triggered in the pen by clicking the “Swap Sources” button. If we only implement bind, the values for data-src and src are not changed when we want them to be!

To implement this, we need to add an updated hook:

const lozadDirective = { bind(el, binding) { el.setAttribute('data-src', binding.value) ; let observer = lozad(el); observer.observe(); }, update(el, binding) { if (binding.oldValue !== binding.value) { el.setAttribute('data-src', binding.value); if (el.getAttribute('data-loaded') === 'true') { el.setAttribute('src', binding.value); } } } }

With this in place, we’re set! Our directive now updates everything lozad touches whenever Vue updates. The final version can be found in this pen:

See the Pen Vue integrations: Lozad Directive With Updates by Kevin Ball.

Components And Component Libraries

The most complex third-party JavaScript to integrate is that which controls entire regions of the DOM, full-on components and component libraries. These tools expect to be able to create and destroy elements, manipulate them, and more.

For these, the best way to pull them into Vue is to wrap them in a dedicated component, and make extensive use of Vue’s lifecycle hooks to manage initialization, passing data in, and handling events and callbacks.

Our goal is to completely abstract away the details of the third-party library, so that the rest of our Vue code can interact with our wrapping component like a native Vue component.

Component Lifecycle Hooks

To wrap around a more complex component, we’ll need to be familiar with the full complement of lifecycle hooks available to us in a component. Those hooks are:

  • beforeCreate()
    Called before the component is instantiated. Pretty rarely used, but useful if we’re integrating profiling or something similar.
  • created()
    Called after the component is instantiated, but before it is added to the DOM. Useful if we have any one-off setup that doesn’t require the DOM.
  • beforeMount()
    Called just before the component is mounted in the DOM. (Also pretty rarely used.)
  • mounted()
    Called once the component is placed into the DOM. For components and component libraries that assume DOM presence, this is one of our most commonly used hooks.
  • beforeUpdate()
    Called when Vue is about to update the rendered template. Pretty rarely used, but again useful if integrating profiling.
  • updated()
    Called when Vue has finished updating the template. Useful for any re-instantiation that is needed.
  • beforeDestroy()
    Called before Vue tears down a component. A perfect location to call any destruction or deallocation methods on our third-party component
  • destroyed()
    Called after Vue has torn down a component.
Wrapping A Component, One Hook At A Time

Let’s take a look at the popular jquery-multiselect library. There exist many fine multiselect components already written in Vue, but this example gives us a nice combination: complicated enough to be interesting, simple enough to be easy to understand.

The first place to start when implementing a third-party component wrapper is with the mounted hook. Since the third-party component likely expects the DOM to exist before it takes charge of it, this is where you will hook in to initialize it.

For example, to start wrapping jquery-multiselect, we could write:

mounted() { $(this.$el).multiselect(); }

You can see this functioning in this CodePen:

See the Pen ue integrations: Simple Multiselect Wrapper by Kevin Ball.

This is looking pretty good for a start. If there were any teardown we needed to do, we could also add a beforeDestroy hook, but this library does not have any teardown methods that we need to invoke.

Translating Callbacks To Events

The next thing we want to do with this library is add the ability to notify our Vue application when the user selects items. The jquery-multiselect library enables this via callbacks called afterSelect and afterDeselect, but to make this more vue-like, we’ll have those callbacks emit events. We could wrap those callbacks naively as follows:

mounted() { $(this.$el).multiSelect({ afterSelect: (values) => this.$emit('select', values), afterDeselect: (values) => this.$emit('deselect', values) }); }

However, if we insert a logger in the event listeners, we’ll see that this does not provide us a very vue-like interface. After each select or deselect, we receive a list of the values that have changed, but to be more vue-like, we should probably emit a change event with the current list.

We also don’t have a very vue-like way to set values. Instead of this naive approach then, we should look at using these tools to implement something like the v-model approach that Vue provides for native select elements.

Implementing v-model

To implement v-model on a component, we need to enable two things: accepting a value prop that will accept an array and set the appropriate options as selected, and then emit an input event on change that passes the new complete array.

There are four pieces to handle here: initial setup for a particular value, propagate any changes made up to the parent, and handle any changes to value starting outside the component, and finally handle any changes to the content in the slot (the options list).

Let’s approach them one at a time.

  1. Setup With A Value Prop
    First, we need to teach our component to accept a value prop, and then when we instantiate the multiselect we will tell it which values to select.
    export default { props: { value: Array, default: [], }, mounted() { $(this.$el).multiSelect(); $(this.$el).multiSelect('select', this.value); }, }
  2. Handle Internal Changes
    To handle changes occurring due to the user interacting with the multiselect, we can go back to the callbacks we explored before — but ‘less naively’ this time. Instead of simply emitting what they send us, we want to turn a new array that takes into account our original value and the change made.
    mounted() { $(this.$el).multiSelect({ afterSelect: (values) => this.$emit('input', [...new Set(this.value.concat(values))]), afterDeselect: (values) => this.$emit('input', this.value.filter(x => !values.includes(x))), }); $(this.$el).multiSelect('select', this.value); },
    Those callback functions might look a little dense, so let’s break them down a little.

    The afterSelect handler concatenates the newly selected value with our existing values, but then just to make sure there are no duplicates, it converts it to a Set (guarantees uniqueness) and then a destructuring to turn it back to an array.

    The afterDeselect handler simply filters out any deselected values from the current value list in order to emit a new list.
  3. Handling External Updates To Value
    The next thing we need to do is to update the selected values in the UI whenever the value prop changes. This involves translating from a declarative change to the props into an imperative change utilizing the functions available on multiselect. The simplest way to do this is to utilize a watcher on our value prop:
    watch: // don’t actually use this version. See why below value() { $(this.$el).multiselect('select', this.value); } }
    However, there’s a catch! Because triggering that select will actually result in our onSelect handler, and thus use updating values. If we do this naive watcher, we will end up in an infinite loop.

    Luckily,for us, Vue gives us the ability to see the old as well as the new values. We can compare them, and only trigger the select if the value has changed. Array comparisons can get tricky in JavaScript, but for this example, we’ll take advantage of the fact that our arrays are simple (not containing objects) and use JSON stringify to do the comparison. After taking into account that we need to also deselect any that options that have been removed, our final watcher looks like this:
    watch: { value(newValue, oldValue) { if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) { $(this.$el).multiSelect('deselect_all'); $(this.$el).multiSelect('select', this.value); } } },
  4. Handling External Updates To Slot
    We have one last thing that we need to handle: our multiselect is currently utilizing option elements passed in via a slot. If that set of options changes, we need to tell the multiselect to refresh itself, otherwise the new options don’t show up. Luckily, we have both an easy API for this in multiselect (the ’refresh' function and an obvious Vue hook to hook into) updated. Handling this last case is as simple as:
    updated() { $(this.$el).multiSelect(’refresh'); },
    You can see a working version of this component wrapper in this CodePen:

    See the Pen Vue integrations: Multiselect Wrapper with v-model by Kevin Ball.

Drawbacks And Other Considerations

Now that we’ve looked at how straightforward it is to utilize third-party JavaScript within Vue, it’s worth discussing drawback of these approaches, and when it appropriate to use them.

Performance Implications

One of the primary drawbacks of utilizing third-party JavaScript that is not written for Vue within Vue is performance — particularly when pulling in components and component libraries or things built using entire additional frameworks. Using this approach can result in a lot of additional JavaScript that needs to be downloaded and parsed by the browser before the user can interact with our application.

For example, by using the multiselect component, we developed above means pulling in not only that component’s code, but all of jQuery as well. That can double the amount of framework related JavaScript our users will have download, just for this one component! Clearly finding a component built natively with Vue.js would be better.

Additionally, when there are large mismatches between the APIs used by third-party libraries and the declarative approach that Vue takes, you may find yourself implementing patterns that result in a lot of extra execution time. Also using the multiselect example, we had to refresh the component (requiring looking at a whole bunch of the DOM) every time a slot changed, while a Vue-native component could utilize Vue’s virtual DOM to be much more efficient in its updates.

When To Use

Utilizing third-party libraries can save you a ton of development time, and often means you’re able to use well-maintained and tested software that you don’t have the expertise to build. The primary drawback is performance, particularly when bringing in large frameworks like jQuery.

For libraries that don’t have those large dependencies, and particularly those that don’t heavily manipulate the DOM, there’s no real reason to favor Vue-specific libraries over more generic ones. Because Vue makes it so easy to pull in other JavaScript, you should go based on your feature and performance needs, simply picking the best tool for the job, without worrying about something Vue-specific.

For more extensive component frameworks, there are three primary cases in which you’d want to pull them in.

  1. Prototyping
    In this case, speed of iteration matters far more than user performance; use whatever gets the job done fastest.
  2. Migrating an existing site.
    If you’re migrating from an existing site to Vue, being able to wrap whatever framework you’re already using within Vue will give you a graceful migration path so you can gradually pull out the old code piece by piece, without having to do a big bang rewrite.
  3. When the functionality simply isn’t available yet in a Vue component.
    If you have a specific and challenging requirement you need to meet, for which a third-party library exists but there isn’t a Vue specific component, by all means consider wrapping the library that does exist.

When there are large mismatches between the APIs used by third-party libraries and the declarative approach that Vue takes, you may find yourself implementing patterns that result in a lot of extra execution time.

“ Examples In The Wild

The first two of these patterns are used all over the open-source ecosystem, so there are a number of different examples you can investigate. Since wrapping an entire complex component or component library tends to be more of a stopgap/migration solution, I haven’t found as many examples of that in the wild, but there are a couple out there, and I’ve used this approach for clients occasionally as requirements have dictated. Here is a quick example of each:

  1. Vue-moment wraps the moment.js library and creates a set of handy Vue filters;
  2. Awesome-mask wraps the vanilla-masker library and creates a directive for masked inputs;
  3. Vue2-foundation wraps up the ZURB Foundation component library inside of Vue components.
Conclusion

The popularity of Vue.js shows no signs of slowing down, with a huge amount of credit being due to the framework’s progressive approach. By enabling incremental adoption, Vue’s progressive nature means that individuals can start using it here and there, a bit at a time, without having to do massive rewrites.

As we’ve looked at here, that progressive nature extends in the other direction as well. Just as you can embed Vue bit by bit in another application, you can embed other libraries bit by bit inside of Vue.

Need some piece of functionality that hasn’t been ported to a Vue component yet? Pull it in, wrap it up, and you’re good to go.

Further Reading on SmashingMag: (rb, ra, il)
Categories: Around The Web
Syndicate content