Code Challenge: Tissue Contrast Illusion

5 minute read

The goal of this exercise is to replicate the Tissue Contrast Illusion in the browser. We’ve broken the problem into logical steps and provided (hidden) sample code if you get stuck. But, keep in mind there are many (and possibly better) ways to solve this problem.

Source: Curiosity Stream

Prerequisites

This activity should be approachable for beginners but some understanding of the following technologies will be beneficial: HTML basics, CSS basics and the CSS box model.

Sandbox environments: Codepen.io is a handy tool (one of many) that helps you code directly in the browser.

Topics covered

The sample code (below) further incorporates the following concepts and technologies: Flexbox, viewport units, linear gradients, transparent images and absolute positioning.

Planning it out

Let’s break down this exercise according to how elements need to overlap:

  1. Two identical grey circles
  2. Under the circles: split-colour background
  3. On top of the circles: semi-transparent image.

These objectives are further broken down below. Remember: sample code is provided in case you get stuck but there are many ways to achieve the desired results.

Sample code

HTML Structure
<!-- The parent-child relationship of the `container` and `item`s is crucial to how Flexbox operates later. -->
<main class="container split-bg">
  <div class="item circle"></div>
  <div class="item circle"></div>
</main>
<!-- Placed at the end so that it sits on top when positioned later. -->
<div class="image"></div>

Objective 1: Create and position two grey circles

Two grey patches
Two identical grey (50% black) patches, each centered in their half of the viewport (i.e. the viewable portion of a web page).

Sample code

Two grey circles
.circle {
  /* make it square */
  width: 30vmin;
  height: 30vmin;

  /* make it visible */
  background: grey;

  /* make it circular */
  border-radius: 50%;
}

Coming from print?: Check out this excellent video by Jen Simmons: Designing for a Viewport.

Pro tip: Viewport units are amazing for global layout but try em/rem units for smaller page elements such as cards).

Each circle, centred in their half of the viewport
.container {
  /* change default behaviour of `margin: auto` below */
  display: flex;

  /* explicitly set height to viewport; `margin: auto` needs room to work */
  height: 100vh;
}

.item {
  /* equally distribute extra horizontal/vertical space among flex items; block elements only do this for `margin-left` and `margin-right` */
  margin: auto;
}

Getting fancy: Check out the Complete Guide to Flexbox for more ways to control your items with properties like justify-content and align-items.

Pro tip: Flexbox is very handy for laying out navigation menus, hero sections and cards.

Objective 2: Add a split-colour background

Two grey patches
Two identical grey patches on a split-colour (very light and very dark) background. Notice the left circle appears slightly darker than the one on the right.

Sample code

Add a split-colour background
.split-bg {
  /* note: the final `background` declaration overrides the others,
  which are included for clarity but can safely be ignored */

  /* basic gradient; default gradient line direction: bottom to top (0deg)  */
  background: linear-gradient(white, black);

  /* change default direction: left to right (90deg) */
  background: linear-gradient(90deg, white, black);

  /* hide gradient area by adding identical colour stops */
  background: linear-gradient(90deg, white 50%, black 50%);
}

Creative text effects: Mandy Michael uses this gradient technique in many of her amazing designs.

Objective 3: Overlap viewport with a semi-transparent image

Two grey patches
Ovelapping a semi-transparent image over top of the viewport enhances the effect of the illusion.

Sample code

Add a viewport-sized CSS image
.image {
  /* explicitly set element size to viewport */
  width: 100vw;
  height: 100vh;
  
  /* add full-size, centered background image to element */
  background-image: url('https://picsum.photos/500/500');
  background-size: cover;
  background-position: center;
}

Alternate Solution: Another valid option is to use an HTML image using an img element with the object-fit property.

Overlap the image
.image {
  /* create a new block formatting context and enable `top` and `left` */
  position: absolute;

  /* explicitly move top-left corner of image to top-left corner of <body> */
  top: 0;
  left: 0;
}

Extra Points: Absolute positioning is the classic method. Try using a newer technique: explicit item placement with CSS Grid. Each have their advantages depending on your situation.

Make the image semi-transparent
.image {
  /* set element opacity to 50% */
  opacity: 0.5;
}

More Transparency: opacity isn’t the only way to create transparency in CSS. Gradients accept transparent as a colour keyword and you can add an alpha channel to rgb() or hsl() when defining a colour.

Cleaning things up with resets

A “reset” is CSS we add to change default browser behaviour to either:

  1. make our projects more consistent across browsers, or
  2. be lazy because we’re tired of setting the same parameters for every project.

For example, you might currently see an irritating horizontal (and/or vertical) scroll bar in your browser window while working through this exercise. This is because most browsers add a default margin to their body tag. When we set the width and height of an element to fill the viewport, this margin is added to the final size of the page (hence, a scroll bar).

Below are two resets that most professional developers will use in every project (there are many more).

Sample code

Reset default margins
body {
  /* remove pesky scroll bars */
  margin: 0;
}
Reset default `box-sizing`
* {
  /* make size calculations take 'padding' and 'border' into account */
  box-sizing: border-box;
}

Optional: box-sizing doesn’t apply to the sample code as written but elements with added padding and border might benefit from this handy reset.

Pro tip: Many professional developers will use a reset library such as normalize.css to ensure consistent results with their projects.

Mobile Considerations

Sure, this illusion seems to work on the laptop but what if you want to text a link to your mom? Try adding a media query that declares the following CSS when the viewport is in the portrait orientation:

  1. Place the circles vertically so one is above the other;
  2. Change the gradient line direction to run from top to bottom.

Sample code

Add support for portrait orientation
@media (orientation: portrait) {
  /* Apply these styles when the screen is in 'portrait' orientation */
  .container {
    /* place circles in an up/down orientation */
    flex-direction: column;
    
    /* change the direction of the split-colour background to match the new direction */
    background: linear-gradient(180deg, white 50%, black 50%);
  }
}

One nail, two hammers: Using display: grid instead of flex-direction: column produces the same results. Why? Because CSS.

Final Demo

See the Pen Exercise Spoilers: Tissue Contrast Illusion by Tony Grimes (@funwithcodeyyc) on CodePen.