Link text for printed web pages

One of the most frustrating things that happen when I print a web page is that all links appear as underlined text without any reference to what the links point out to.

One way to solve the problem without Javascript is to use generated content to append the full URL to the link and use print specific stylesheets so that it’ll only happen when we print the document.

Dudley Storey provides another way to accomplish the same goal using Javascript. It doesn’t require an additional stylesheet but, depending on the length of the document, may be hard to move back and forth between the link and the list of URLs at the bottom of the page.

The Stylesheet

The additions for link descriptors for paged media is straightforward. The following rule will append the URL In parenthesis to all the links in the page.

It uses attribute selectors that will match all a elements with an href attributes that begin with http.

It will then use the :after pseudo element to append the string (, the href attribute and the string ) to the element. The spaces are significant.

a[href^="http"]:after {
  content: " (" attr(href) ")";

If you’re working in https://example.com and don’t want to add the URL to local links you can modify the rule as follows:

a[href^='http']:not([href*='example.com']):after {
  content: ' (' attr(href) ')';
}

The new rule adds :not to indicate that we don’t want to apply the rule if the href attribute contains example.com anywhere.

And that’s it we have

Using the print styles

When it comes to using print media styles we have two options.

We can create a print-specific stylesheet and link it from the document. This adds an HTTP roundtrip but shouldn’t block the rendering of the page since the media for it is not screen.

The only change we need to make to the regular way we link stylesheets is to add the media="print" attribute.

<link rel="stylesheet" media="print" href="print.css">

If we’re making just a few changes to the stylesheet then we can include them in an existing stylesheet by putting them inside a @media print block.

@media print {
  a[href^="http"]:after {
    content: " (" attr(href) ")";
  }

If we add information to links in the page we may also want to add more print-related enhancements like those in Creating Print CSS stylesheets.

Sadly browser support for paged media and most of the generated content for paged media specs is severely lacking, even in browsers that support reading ebooks in the browser (Edge). So we have to work around these deficits by being creative in how we format our content to print from the web.

Links and resources

Multicolumn Layouts

It’s been possible to work in multiple columns of text without having to resort to hacks to make columns work in multiple browsers and in multiple form factors.

According to caniuse.com developers need to pay attention to the following caveats:

  • Firefox doesn’t support the break-before, break-after, break-inside properties but supports the page-break-* properties to accomplish the same result.
  • WebKit-based based browsers support the non-standard -webkit-column-break-* properties to accomplish the same result (but only the auto and always values)
  • Before Chrome 63, the browser supports the non-standard -webkit-column-break-* properties to accomplish the same result (but only the auto and always values)
  • Samsum Internet version 4 does not support the column-fill property. This has been fixed for version 7

None of these issues are deal breakers, we just have to write more CSS to accomplish the same goal but it’s a good use case for feature queries or working with the cascade where browsers will ignore rules that they don’t understand.

Looking at the source of the article I can see why the disappointment.

Let’s start with the basics.

We can specify columns either by indicating a number of columns, meaning that we will always have the same number of columns, regardless of how wide the display is:

#column1 {
  border: 2px solid #000;
  column-count: 3;
}

We can also specify how wide we want the columns to be. The smaller the width of the screen the fewer columns we’ll get until it turns to a single column, responsive by default 🙂

#column2 {
  border: 2px solid #000;
  column-width: 20em;
}

We can also specify the gap between the columns using the columns-gap descriptor and can add a rule using column-rule.

In this example, we make the columns 15em wide with a 2em gap between the columns and solid green rules between them

#column3 {
  column-width: 15em;
  column-gap: 2em;
  column-rule: 4px solid green;
  padding: 5px;
}

If we want columns of equal height we set up an explicit height and then use column-fill to make sure that they are all the same height (where possible).

#column4 {
  column-width: 15em;
  column-gap: 2em;
  column-rule: 4px solid green;
  padding: 5px;
  column-fill: balance;
  height: 400px;
}

The final aspect of multi-column layout that I want to cover is items spanning multiple columns. These may be subtitles or images that we want to cover the more than one column of text.

#column5 {
  column-count: 4;
  column-gap: 2em;
}

#span-content {
  column-span: all;
  border: 5px solid #0000ff;
}

So what does it look like when put into a page? The full example shows the end result of the process. The relevant CSS code (shown as uncompiled SCSS) looks like this:

.container {
  margin: 0 auto;
  width: 80%;
}

.columns {
  columns: 2;
  column-gap: 2em;
}

.columns3 {
  columns: 3;
  column-gap: 2em;
}

figure {
  margin: 3em 0;

  img {
    width: 100%;
    height: auto;
  }
}

In this code, the container class will center the element in the page.

The columns class will create 2 columns with a 2em gap between the columns.

columns3 will create 3 smaller columns.

The figure element selector sets the top and bottom margin while the nested img selector makes the width 100% and the height automatic. This makes the image responsive to the width of the window.

Because we’ve used fixed column numbers we need to make sure that we adjust the columns and values using media queries.

If the screen is narrower than 640px then we want all text to be in a single column. Since the image is responsive we don’t need to worry about changing it in the media queries.

@media screen and (max-width: 640px) {

  .columns,
  .columns3 {
    columns: 1;
  }
}

The next screen resolution is between 641 and 800 pixels wide. We set the text above the image to be a single column and the text below to two columns.

@media screen and (min-width: 641px) and (max-width: 800px) {
  .columns {
    columns: 1;
  }

  .columns3 {
    columns: 2;
  }
}

This looks convoluted and confusing but we have to do it so it’s readable in tablets, phones and other form factors we may have never heard of.

Revisiting design systems

Design systems are interesting because of how they scale design to multiple teams and multiple sites/products while still providing a consistent and recognizable brand.

For a full introduction to design systems, see Introducing Design Systems

The work I do is not always conducive to creating a design system but it’s always good to know about them so we can jump in without having to start completely from scratch.

That said we’ll look at three areas:

  • Building a basic component using just HTML, and CSS
  • Building the same component using existing design systems
    • Material Design from Google
    • Atomic Design from Brad Frost
  • Building the component using Web Components
    • Plain Web Components
    • Polymer

No Design Systems

I want to design a card component where we can control the direction (vertical or horizontal), the dimensions (height and width) and the border. The component should also provide ways to layout its children using Flex.

The CSS looks something like this:

:root {
  --card-border-color: #000;
  --card-border-width: 1px;
  --card-border-style: solid;
  --card-display: flex;
  --card-direction: column;
  --card-width: 400px;
  --card-height: 300px;
  --card-padding: 1em;
}

.card {
  display: var(--card-display);
  flex-flow: var(--card-direction);
  border: var(--card-border-width) var(--card-border-style) var(--card-border-color);
  height: var(--card-height);
  width: var(--card-width);
}

.card-title {
  color: #fff;
  background-color: #000;
}

.card-content {
  padding: var(--card-padding);
}

The idea is that by changing the value of the variable declared in the :root element we change the way that all card elements will look on the page. I choose the : root element rather than HTML because :root has higher specificity and it would override any declarations in HTML rather than having to depend on the way properties were declared every time.

Let’s say that we want to create a horizontal card. All we have to do is redefine the variables for the elements we need to change, something like the following example:

In this example, we make the card layout its children in a row, rather than a column, we also change the dimensions of the card but we keep all other values the same as our :root declaration.

:root {
  --card-border-color: #000;
  --card-border-width: 1px;
  --card-border-style: solid;
  --card-display: flex;
  --card-direction: column;
  --card-width: 400px;
  --card-height: 300px;
  --card-padding: 1em;
}

.card2 {
  --card-border-color: #000;
  --card-direction: row;
  --card-height: 300px;
  --card-width: 800px;

  border: var(--card-border-width) var(--card-border-style) var(--card-border-color);
  display: var(--card-display);
  flex-flow: var(--card-direction);
  height: var(--card-height);
  width: var(--card-width);

  .card-title {
    width: 10em;
  }
}

This is just an initial step, we could further refine the card component by providing defaults in case we want to add an image instead of text on the title, but it’ll do as the basic example of what we want to accomplish.

One other thing that needs to happen for this card to become an element is to document the ways in which you can customize the component… ideally with full examples that can be copy and pasted to be used.

Design System

Now that we have a basic idea of what we want to do, let’s look at different ways in which design systems can make the process easier, less painful or both.

Material Design

The new iteration of material design has its good and bad aspects.

The good is that the new version is much easier to customize and make look “less like a Google product” than the original

The bad is that Material Design now requires SCSS and ES6 for development. You can’t just drop the Material Design components into an existing project if the project doesn’t use a build system or SASS to process the CSS.

Since I already use both ES6 and SCSS for my projects, I think the drawbacks, if you can call them that, are worth the investment.

As you can see the code depends heavily on classes starting with mdc-. The reason why will become clearer when we look at the SCSS code that styles the element.

<div class="mdc-card mdc-card--outlined my-card ">
  <div class="mdc-card__media mdc-card__media--square">
    <div class="mdc-card__media-content">Title</div>
  </div>
  <p>Content goes here</p>

  <div class="mdc-card__actions">
    <div class="mdc-card__action-buttons">
      <button class="
        mdc-button
        mdc-card__action
        mdc-card__action--button">Action 1</button>
      <button class="mdc-button
        mdc-card__action
        mdc-card__action--button">Action 2</button>
    </div>
  </div>
</div>

Material design is based on SCSS so to add the components’ styles we just import them using SASS @import syntax… with a twist.

@import '@material/card/mdc-card';
@import '@material/button/mdc-button';

body {
  font-family: Roboto, sans-serif;
}

.mdc-card {
  margin: 2em auto;
  height: 350px;
  width: 350px;
}

.mdc-card__media {
  background-image: url('../images/sunrise.jpg');

  img {
    width: 350px;
  }
}

Because we are going into the node_modules directory we have to call the sass command with a special flag to include the modules directory into the path SASS searches. The command is:

sass -I node_modules/ sass/:css/

This works in version 1.13.0, the current Dart version of SASS.

Atomic Design

Atomic Design, conceived by Brad Frost, and explained in the book of the same name is a way to break a page into its components down to the single HTML element. The introduction of the book explains it like so:

And then Brad Frost the front-end developer started talking like Brad Frost the chemist. He talked about atoms and molecules and organisms—about how large pieces of design can be broken down into smaller ones and even recombined into different large pieces. Instead of visualizing the finished recipe for the design, in other words, he was showing us the ingredients. And we lit up: this was a shift in perspective, a way to move away from conceiving a website design as a collection of static page templates, and instead as a dynamic system of adaptable components. It was an inspired way to approach this responsive website—and all responsive projects for that matter.

Atomic Design — Introduction.

Atomic design uses three structures to build pages: Atoms, molecules, and organisms.

Think of individual HTML elements as atoms, the basic building block of a web page. You can’t break down an atom into smaller parts without losing functionality and the effect the element has on the page.

Look at the table below to get an idea of what the possible atoms of a web page are.

Periodic Table of HTML Elements shows that all HTML elements are organized around a function.

These atoms may be the plain elements or styles for a specific theme or molecule designs.

The next step up is a ** molecule**. We create a molecule when we combine two or more atoms to perform a given task.

Organisms are groups of molecules functioning together as a unit. These structures can be as small or as large as you need them to be. We can use these organisms to build pages and applications.

For more information about Atomic Design, check the the book.

For the card organism, we’ll take the same card declaration that we used when we created the card component.

We have two molecules making the card organism:

  • A card-title molecule that contains a title and, optionally, an image inside
  • A card-content molecule that contains the text content

Each of these molecules can have any number of atoms inside of them. We can choose to document individual atoms or to just to document the styles for colors, links, and typography, among others.

<div class="card">

  <div class="card-title">
    <h1>Card Title</h1>
  </div>

  <div class="card-content">
    <p>I want to design a card component</p>
  </div>

</div>

The styling is the same as for the no-design-system using CSS variables to handle different alternatives for the content and some effects on the text and background colors of the molecules.

:root {
  --card-border-color: #000;
  --card-border-width: 1px;
  --card-border-style: solid;
  --card-display: flex;
  --card-direction: column;
  --card-width: 400px;
  --card-height: 300px;
  --card-padding: 1em;
  --card-title-color: #fff;
  --card-title-background-color: #000;
}

body {
  font-family: Roboto, sans-serif;
  width: 80%;
  margin: 0 auto;
}

.card {
  display: var(--card-display);
  flex-flow: var(--card-direction);
  border: var(--card-border-width) var(--card-border-style) var(--card-border-color);
  height: var(--card-height);
  width: var(--card-width);
}

.card-title {
  color: var(--card-title-color);
  background-color: var(--card-title-background-color);
}

.card-content {
  padding: var(--card-padding);
}

Web Components As Design Systems

Web components, to me, address some of the shortcomings of design systems.

They encapsulate styles so there’s no bleeding between the containing page and the components, this means that styles across different components can have the same name as they are exclusive to the element… We’ll explore the advantages and disadvantages of the model.

Vanilla JS

The vanilla Javascript version consists of two parts: The CSS and HTML that we’ll stamp every time we create an element of this kind.

We use CSS variables that we’ll define in the HTML container for the element.

let template = document.createElement('template');
template.innerHTML = `<style>
  .card {
    width: 80% ;
    margin: 0 auto;
    display: var(--card-display);
    flex-flow: var(--card-direction);
    border: var(--card-border-width)
            var(--card-border-style)
            var(--card-border-color);
    height: var(--card-height);
    width: var(--card-width);
  }

  .card-title {
    color: var(--card-title-color);
    background-color: var(--card-title-background-color);
  }

  .card-content {
    padding: var(--card-padding);
  }</style>

The HTML portion of the template uses slots to reflect elements of the ‘light side’ declaration to places in the template.

The slots also provide additional ways to style the content.

We’ll look at slots in more detail on the slotted content when we look at the implementation of the card element.

  <div class="card">
    <div class="card-title">
      <slot id="title" name="title"><h1>Title</h1></slot>
    </div>

    <div class="card-content">
      <slot id="content" name="content"><p>Content</p></slot>
    </div>
  </div>
`;

The second part of the card-element element is the definition of the custom element itself. We do it by creating a class that extends HTMLElement, the base class for all HTML elements.

We then build the constructor where we do the following:

  1. Call the parent’s class constructor using super()
  2. create a shadow root for the element
  3. Append the content of the template to the shadow root we just created

Finally, we use the define method of the customElements registry to associate a tag name with the component we just defined.

class cardComponent extends HTMLElement {
  constructor() {
    // Allways call super() first
    super();
    let root = this.attachShadow({
      mode: 'open',
    });
    root.appendChild(template.content.cloneNode(true));
  }
}

window.customElements.define('card-element', cardComponent);

With that, we’re ready to implement the element.

In the head of the element, I’ll place a link to Google Fonts and a set of variable declarations for the :root element.

<link rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i">
<style>
:root {
  font-family: Roboto, sans-serif;
  --card-border-color: #000;
  --card-border-width: 1px;
  --card-border-style: solid;
  --card-display: flex;
  --card-direction: column;
  --card-width: 400px;
  --card-height: 300px;
  --card-padding: 1em;
  --card-title-color: #fff;
  --card-title-background-color: #000;
  }
</style>

We then import the card element as a module using the type=module attribute of the script tag.

<script type="module" src="wc-card.js"></script>

In the body of the page, we stamp out multiple versions of our card-element element. The first one is just the element. This will create a card with only fallback content.

<card-element></card-element>

The second element uses the slots we defined in the element’s template. We can add as much content to each slot as we want… as far as I know there is no limit.

<card-element>
  <div slot="title">
    <h1>Card Demo #2</h1>
  </div>
  <div slot="content">
    <p>Some content that is different from our default</p>
  </div>
</card-element>

Polymer

Polymer adds syntactic sugar on top of web components with additional functionality and syntactic sugar. The idea is to drop these components.

Building a Polymer element is very similar to building a vanilla Javascript web component. Instead of extending from HTMLElement we extend from PolymerElement, the base class for Polymer.

Since we’re not using any binding in the element, we can use a template like the one we created for the vanilla components.

The main difference is that we must import the html tagged template literal and PolymerElement base class before we can use them anywhere.

import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';

class MyCard extends PolymerElement {
  static get template() {
    return html`
  <style>
  :host {
    width: 80% ;
    margin: 0 auto;
    font-family: Roboto, sans-serif;
    --card-border-color: #000;
    --card-border-width: 1px;
    --card-border-style: solid;
    --card-display: flex;
    --card-direction: column;
    --card-width: 400px;
    --card-height: 300px;
    --card-padding: 1em;
    --card-title-color: #fff;
    --card-title-background-color: #000;
  }

  /* Rest of the CSS Removed for brevity */
  </style>

  <div class="card">
    <div class="card-title">
      <slot id="title" name="title">
        <h1>Title</h1>
      </slot>
    </div>

    <div class="card-content">
      <slot id="content" name="content">
        <p>Content</p>
      </slot>
    </div>
  </div>
`;
  }
}

window.customElements.define('my-card', MyCard);

The host document is almost identical to the plain vanilla web component. We add the following elements to the head of the element.

We include the wecomponents polifill to make sure that the component works in browsers without native support.

<link rel="stylesheet" href="https://fonts.googleapis.com/css?
family=Roboto:400,400i,700,700i"/>
<style></style>
<script src="../node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
<script type="module" src="../my-card.js"></script>

The Polymer version of the element is identical to the vanilla component. We insert the following into the body of the page.

<my -card>
  <div slot="title">
    <h1>Card Demo #2</h1>
  </div>
  <div slot="content">
    <p>Some content that is different from our default
       so we can be sure that the slots are working as
       intended and we are not pulling the default content</p>
  </div>
</my>

Documenting Your Design System

Now that we’ve decided to use a design system and we’ve chosen the components that we want to share. Now we need to document it.

Fractal provides tools and structure for design systems. We will not address the specifics of Fractal or any other tools to document.

I chose Fractal because it gives you the flexibility to use components with copy/paste or incorporating into a build system.

Fractal’s downside is the documentation. It leaves a lot to be desired when it comes to documenting the process to incorporate the tool into other applications, the API and mount points for the application.

There is ongoing work to improve and update Fractal. See issue 449 in the Fractal Github repository for more details as to current status.

Looking forward: AI Design Systems?

An interesting take on design systems is what would it look like if we can get working code from a hand-drawn wireframe?

There are projects in early prototype stages that will let you do this.

Pix2Code has published both a paper
and the corresponding Github repo explaining their work.

According to the paper:

Transforming a graphical user interface screenshot created by a designer into computer code is a typical task conducted by a developer in order to build customized software, websites, and mobile applications. In this paper, we show that deep learning methods can be leveraged to train a model end-to-end to automatically generate code from a single input image with over 77% of accuracy for three different platforms (i.e. iOS, Android and web-based technologies).

pix2code: Generating Code from a Graphical User
Interface Screenshot

This would mean that the approximation we craft can be translated to code that can be further iterated on or can be put into production.

In an interesting twist, Airbnb is also looking at using Machine Learning to translate mockups into code that can be further moved up and down the design and development chain.

There is no actual code prototype but the idea is intriguing enough to merit further attention and research.

Links and Resources

Using Counters in CSS

I’ve always wanted to avoid manually doing data replacement and numbering if I can avoid it.

We’ll add counters for the following:

  • Chapters defined by <h2> elements
  • Sections defined by <h3> elements
  • Figures will be tied to chapters
  • Tables will be tied to chapters

We’ll work with the headings first and then add whatever we need to so that figures and tables work as designed.

Chapters, sections, and subsection

The basic structure of the styles is the same: If needed we reset counters using counter-reset and increase the appropriate counters using counter-increment.

We then use the ::before pseudo-element to write the counter the way we want it to appear on the page.

h2 {
  counter-reset: chapter, section, subsection;
  counter-increment: chapter;
}

h2::before {
  content: 'Chapter ' counter(chapter) ': ';
}

h3 {
  counter-reset: section, subsection;
  counter-increment: section;
}

h3::before {
  content: 'Section ' counter(chapter) '.' counter(section) ' ';
}

Figures and tables

We only need to change one thing in our previous code to make it work with tables and figures.

In the counter-reset for chapters, we need to add figures and tables

h2 {
  counter-reset: chapter, section, subsection, figures, tables;
  counter-increment: chapter;
}

The figure and table rules are similar to what we already cover. The figure and table elements increase the counters and figcaption and caption use ::before to display the counter text and values

figure {
  counter-increment: figures;
}

figcaption::before {
  content: 'Figure ' counter(chapter) '-' counter(figures) ': ';
}

table {
  counter-increment: tables;
}

caption::before {
  content: 'Table ' counter(chapter) '-' counter(tables) ': ';
}

I realize that this is cumbersome but, as with many things in CSS, we trade complexity for Flexibility.

We can change the type of counters to uppercase Roman characters by changing the declaration of the counter to make it match the numbering schema that we need.

In the example below, we’ve changed the numbering schema to use an Arabic number for chapters and lowercase Roman numerals for the tables. The example would look Table 2-A:

table {
  counter-increment: tables;
}

caption::before {
  content: 'Table ' counter(chapter) 
  '-' counter(tables, upper-alpha) ': ';
}

Building a 3D scene

3D content is a really interesting way to create interactive content for the web but until recently it has been a pain to develop on a Mac, particularly since most device makers decided early on that Macintosh hardware wasn’t powerful enough to work with their devices (see, this post from 2015 outlining the minimal hardware support level and, partly, why it won’t work with Apple hardware) and, though things seem to be getting better with newer Apple hardware releases, I’m not holding my breath and neither is Macworld UK.

While direct development with the Oculus SDK is out of the question we can always fall back into libraries that sit on top of the raw WebGL spec and allow us to use the API without having to worry about Shaders and Matrix algebra.

We’ll look at the basics of two frameworks/libraries:

  • Three.js one of the best 3D and VR frameworks available
  • A-Frame, a project from Mozilla that uses declarative markup to create 3D content

Finally, we’ll look at the WebXR Device API specification as a unified toolkit for 3D immersive media on the web along with some ideas that are soon to hit the prototype stage soon.

Three.js

Three.js sits on top of WebGL and provides abstractions to most WebGL primitives and the ability to drop to the low-level shaders and functions when needed.

I’m further developing the full example in Glitch where you’re more than welcome to remix it for your own needs.

The code

The first part of the code sets up basic variables to set up the environment for the scene.

THREE.Scene() defines the environment that will be rendered. This is the root of the Three.js application.

THREE.PerspectiveCamera defines how the scene will be rendered. The camera takes 4 parameters

  • fov — Camera frustum vertical field of view.
  • aspect — Camera frustum aspect ratio.
  • near — Camera frustum near plane.
  • far — Camera frustum far plane.

These four parameters together define the region of the 3D image that appears on the screen (the viewing fustrum)

THREE.WebGLRenderer tells Thre.js to use WebGL to render the scene. There are other renderers available.

We set the size of the scene to be the full height and width of the screen. We use innerWidth/innerHeight to make sure we take into account the dimensions of the browser’s chrome.

Lastly, we attach the renderer to the page using appendChild.

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

At the most basic level, objects in Three.js are made of two items: a geometry and a material.

The geometry tells Three.js what shape the object has: a sphere or a plane in this example. We then add each individual object to the scene by calling scene.add with the new object we want to add as a parameter.

const geometry = new THREE.SphereGeometry( 2, 32, 32 );
const material = new THREE.MeshBasicMaterial({
  color: 0x0000ff,
  wireframe: true
});
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);

const geo = new THREE.PlaneGeometry(36, 36, 36);
const mat = new THREE.MeshBasicMaterial({
  color: 0xff00ff,
  side: THREE.DoubleSide });
const plane = new THREE.Mesh(geo, mat);
scene.add(plane);

We next set the position of the objects. In this case, we set the sphere and the plane on an X, Y, Z coordinate system and then rotate the plane 30 units so it looks flatter than the sphere.

sphere.position.set(0, 1 , 0);
plane.position.set( -1, -1, 0);
plane.rotateX( 30 );

The render function is where the animations happen. We call requestAnimationFrame() to animate the next frame of animation. Then we change the rotation of the x and y axes and render the scene with the camera we defined earlier.

The last step is to call the render() function to kick off the animation for the first time.

const render = () => {
  requestAnimationFrame(render);
  sphere.rotation.x += 0.01;
  sphere.rotation.y += 0.01;
  renderer.render(scene, camera);
}
render();

A-Frame

A frame sits on top of Three.js with a declarative API based on tags, like HTML, to declare the components of a 3D scene. Another thing that A-Frame does that Three.JS doesn’t do by default provides a VR-Ready experience for the content we create without writing Javascript.

Some things that I’ve had to keep in mind while working with A-Frame:

A-Frame uses a different system to position content. It uses a right-handed coordinate system where the negative Z-axis extends into the screen.

Nested elements and their positioning in their relation to their parents.

See the position attribute that is common to all A-Frame elements.

Because A-Frame extends Three.js it’s important to understand the relationship between the two. In particular:

  • A-Frame’s <a -scene> maps one-to-one with a three.js scene.
  • A-Frame’s </a><a -entity> maps to one or more three.js objects.
  • three.js’s objects have a reference to their A-Frame entity via .el, which is set by A-Frame.

This example creates a similar scene than the one we created with Three.js.

Note how the elements are nested, the sphere, the plane, and a sky are nested inside the scene element; and the animation for the sphere is nested inside the sphere that is being animated.

  <a-scene>
    <a-sphere
        position="0 2 -4"
        radius="1.25"
        color="teal"
        wireframe="true"
        rotation="0 90 0">

        <a-animation
          attribute="rotation"
          dur="500"
          from="0 0 0"
          to="0 360 0"
          repeat="indefinite"></a-animation>
    </a-sphere>

    <a-plane
      position="0 0 -4"
      rotation="-90 0 0"
      width="12" height="12"
      color="#7BC8A4"></a-plane>

    <a-sky color="#ECECEC"></a-sky>
  </a-scene>

Other APIs: WebXR

The final API that I want to discuss in this post is WebXR a new API that handles both VR and AR using the same API.

What I found most intriguing about WebXR is the possibility of creating “magic windows” that will work based on the device’s capabilities. If the device doesn’t have a 3D-enable viewer attached to it then nothing happens and you see a 2D image of the experience.

If the device has a 3D viewer attached then the user can select to go full VR or AR on the experience. This is particularly cool when working with AR because I can then place and interact with the object anywhere around me.

Chrome continues to run an Origin Trial for WebXR to gather feedback. You can enable the feature on individual versions of Chrome by enabling the “Experimental Web Features” flag in settings.

The purpose of the trial is to gather feedback from developers as they work on finalizing the API to push standardization.