Code Challenge: Tissue Contrast Illusion
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:
- Two identical grey circles
- Under the circles: split-colour background
- 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
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
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
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:
- make our projects more consistent across browsers, or
- 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:
- Place the circles vertically so one is above the other;
- 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.