Code Tutorial #1: Caliscope (1/3)

Hi everyone! Today, I begin a new type of articles on my blog: the “Code Tutorial” series. In these posts, I will share step-by-step tutorials about some of the projects I show on my website. This week, let’s start with my most recent app: the Caliscope!

The tutorial will be in three parts:

  • Part 1 (this article): setting up the app in a local setup and preparing the main elements
  • Part 2: loading images, adding a moves history and an “auto play mode”
  • Part 3: serving the app in production mode (with a Golang server) and deploying it using Docker

Note: this tutorial is not a tutorial on HTML, CSS and Javascript. For a more in-depth course on HTML, you can check out OpenClassrooms (for French speakers).

What is the Caliscope about?

My last collaboration with the abstract painter Cali Rezo was another art-showing app in the form of a 3d virtual studio. It was very interesting to work on and taught me a lot, however it required several weeks of work and only works on computers (this 3d is too computationally intensive for mobiles to handle it, apparently!).

So, this time, Cali and I decided to go for something smaller and, most importantly, to primarily aim at mobile user. The goal was to make an app as lightweight as possible, only with native web technology, that could show paintings in a fun and intuitive way. Here is a demo of the final result:

 

What technologies does the Caliscope use?

For this project, I wanted to use very native web technologies (vanilla HTML, CSS and Javascript). In their latest versions, they provide us with almost all the features with need.

HTML, or HyperText Markup Language, is the core language for web programming. It allows you to define the structure of your web page using “tags” written between < and > characters. As you can see, every “opening” tag has a matching “closing” tag. Tags are nested into each others so as to create a global hierarchical layout. Visually, you can see each inner block as a little element inside its parent container.

This programming language is the one that is understood by browsers which is why all pages you find on the Internet are ultimately just some flavour of HTML.

HTML is the core web language but it is almost always used with its close friends: CSS and Javascript. CSS (or Cascading Style Sheet) defines the visual style of the app: colors, fonts, margins… Javascript on the other hand allows you to create user interaction and add dynamics to your page.

The only add-on we will need to include is a vendor Javascript library called hammer.js to easily get the user’s mobile swipe inputs. To add it to our project, we can either:

  • directly reference the online minified version: plenty of Javascript libraries are hosted on CDNs (Content Delivery Networks) and can therefore be accessed very easily, just by pointing to their URL
  • download this script and add it to our project locally, then point to this local version: this is usually better for production packaging because it avoids your program being dependent on a connection to the CDN for it to work – even if the online version of the script is inaccessible, your code will still have all the info it requires

For now, we’ll just point to the minified version in our HTML index. However, in the last article, when we actually put the app in production, we’ll make sure to download the script and add it in a vendors/ folder that is part of our project.

Setting up the project

In this first article, we just want to run the project locally. So we basically need the usual index.html plus some static assets like styles, scripts and images. The project will have the following structure for now:

To avoid having too much changes between our local and production setups (in particular for assets loading), let’s think ahead and already emulate a small web server (similar to the one we will ultimately have in production mode). There are various tools to create a quick-and-lightweight development web server. I like to use Python’s simple HTTP server. If you have Python installed, it is built-in, so you just need to run one of the following command lines in a terminal, in your project folder (where your index.html is located):

# for Python 2.X
python -m SimpleHTTPServer 8000
# for Python 3.X
python3 -m http.server 8000

Note: make sure that your current working directory is the Caliscope project folder because the simple server considers its current directory as the root of the server.

This command will start a local web server on the port 8000 which can be accessed by going to the following URL: http://localhost:8000. Now, every time you modify the code, you will just have to refresh this URL in your browser and you’ll see your changes directly.

This is also useful because, in our case, we want to make a mobile app. Thus, we’d like to test our changes while in development mode on our phone. With this small webserver, it possible! To check out the app on your phone, open up a browser on your mobile. Then, find your local IP address. Finally, go to your custom url: http://<YOUR_IP_ADDRESS>:8000.

The index.html file is called the “entrypoint” of your project: it is what the browser will load and run by default. As customary in an HTML file, styles should be included in the <head>; scripts on the other hand can be included in the <head> to be loaded before the page, or included at the end of the <body> to be loaded after the page. This second case will be pretty useful for us because it will let our script access elements created by the HTML (that would exist yet if we placed the import in the <head> tag).

Creating the cube

The first thing to do is to actually create our Caliscope cube. It will only be HTML, CSS and Javascript: thanks to the latest CSS features, we can simulate a 3d scene in perspective; and thanks to HTML and Javascript, we can create nice animations on various user inputs to interact with this cube.

Let’s put the following in our (currently empty) index.html file:

[snippet slug=201030_caliscope-tuto_01 lang=html]

When we reload, we don’t get a cube but a list of the words we want to write on the 6 faces: FRONT, BACK, etc. – that’s because, for now, our style.css file is empty! Thus this basic HTML snippet is simply read and displayed by the browser without any fancy customization. The following CSS is to add in our style.css file to make the cube appear:

[snippet slug=201030_caliscope-tuto_02 lang=css]

This piece of CSS code is quite a mouthful! I won’t get into all the details, but I’ll note that it uses 3 nice CSS features:

  • CSS variables: here, we declare variables in the :root block to reuse them later – the advantage is that if we want to change the size of the cube, we only need to update the value in one place
  • 3d simulation: with CSS3, we got the ability to mimic 3d rendering with perspective, deformations, translations, rotations…
  • CSS transitions: in the .cube class style, we see that there is a transition: transform 0.65s; field. The CSS transition key is a pretty cool of smoothly transitioning between two states whenever the value of one or more given properties change: instead of abruptly going from one value to another, the element will interpolate (with respect to the duration you specified in seconds) between its current and its new value, thus creating a nice and fluid transition

So the main idea is to create a (pseudo) 3d scene to place our cube in and then to apply rotations and transformations on our cube faces to simulate the 6-faces dice shape. So far, here is what the project looks like:

A first visual of our app: we placed the basic elements of our layout and added some CSS styling to simulate a 3d effect for the cube

Rotating the cube with swiping!

Well, in truth, we only see one face at the moment, and the rest is only visible because we put semi-transparent colors on the cube. The 3d effect is clearly not top-notch: we want the cube to move! To add user interaction in our page, we need to use our script.js file and to import it in our HTML index. We also need the hammer.js library (here I am using the minified version, directly from the CDN):

[snippet slug=201030_caliscope-tuto_03 lang=html]

We simply add the import of the Javascript file, and we encapsulate our entire “scene” div into a larger one called “container”. This will allow us to manipulate the cube more easily by providing a larger element to swipe on!

In the script.js file, we can now add some of our logic to have the cube actually move:

[snippet slug=201121_caliscope-tuto_04 lang=javascript]

Overall, this script is quite straight-forward. When the window has finished loading, we run a start() function that prepares all the hammer.js logic for swipe events: whenever we swipe in a direction, we rotate the cube to that specific direction. Rotation is done by changing CSS classes – thanks to the power of CSS transitions and animations, this gives us a sweet and smooth motion between various cube states. If you refresh the page, you’ll see that whenever you click and drag your screen, the cube will move! Sadly, for now, it’s not perfect: every time you rotate right, it goes to the “right” face… even when it should actually go back to the “front” one.

Next step is to devise a little trick to get rid of this problem.

For the ones who want a bit more details, we can shine a light on some nice JS features that are shown here:

  • DOM traversal and HTML elements selection: the querySelector() function is an interesting method of the built-in document object in browser Javascript – it allows us to directly access and reference an HTML element in our page by using the classic CSS selectors we are used to (i.e. strings starting with hashes # for ids, with dots . for classes…). More generally, the document object is the global interface to speak to the HTML of our page from our Javascript.
  • variable scoping: when you write a program, you (almost) always use variables to store information more clearly (these variables let you “tag” a specific part of your computer’s memory so that it can be read from and written from painlessly). An important concept with variables is scope or in other words: where is the variable defined and accessible in your code? Global variables are available everywhere whereas local variables are only valid in parts of your program. In JS, the keywords var and let are two ways of defining variables that differ with regard to scope.
  • template literals: you often want to create strings that contain the value of some of your variables (e.g. to show the score of a player in a game, to show how many products remain in an online store, etc.). In Javascript, you can do so easily by using template literals: those are written between backquotes ` and let you instantaneously execute whatever piece of JS you want and have the result be automatically integrated in the string: this is what we’re doing with the `show-${direction}` piece, we simply include the value of the variable direction in a string to form the string “show-left” for example.
  • arrow functions: with ES6, the 2015 version of Javascript, the language introduced a new tool called “arrow functions” that gives us a shorter and simpler way to write basic functions in the form (param1, param2) => result. There are, however, differences with traditional functions (of the form: function(param1, param2) { return result }) that are well explained in this article by J. Orendorff.

At this point, the app looks like the following:

We can now interact with the cube: when we swipe the screen, the cube rotates to another face with a smooth CSS transition! But it’s not yet rotating properly…

The trick: faking the movements

As we just saw, the code currently results in some weird movements whenever we don’t start from the “front” face. This is because we wrote up a very simple rotate() function that assumes the given direction directly corresponds to the face of the cube with the same name – which is true for the “front” face, but not for the others.

At that point, we have two possibilities:

  • either we infer from the current face the correct face to switch to (but this will require quite complex conditions!)
  • or we “trick” the cube to believe the face in front of us is always the “front” face

Remember that, in the end, we won’t have “LEFT”, “RIGHT” and all written on our faces. Instead, we will have images (in my case, Cali’s paintings). So the trick I used is the following:

  • first, we smoothly rotate from the “front” face to a new face
  • at the moment we settled on our new face, we instantly go back to the “front” face (this time, we cancel any smooth transition so that truly happens in the blink of an eye)
  • we make sure that the “front” face has the same image as the new face, so that the switch is invisible for the user

Ready? Right, to do this we need to do 2 things: first, add a notransition class to our CSS that cancels all smooth movements and restores “normal” instantaneous changes; second, a block of code in our JS rotate() function to execute at the end of the rotation movement that will reset the cube to its “front” face.

Here are the two snippets of code after update (I only show the parts that are modified):

style.css

[snippet slug=201121_caliscope-tuto_05 lang=css]

script.js

[snippet slug=201121_caliscope-tuto_06 lang=javascript]

The setTimeout() Javascript built-in is an extremely useful way of running functions at a specific moment in time rather than immediately when the line of code is processed. More precisely, it allows you to tell the program: “ok, see this function? run it in X milliseconds”. This way, you can prepare actions and have them executed after a while. In our CSS stylesheet, we defined the transitions to last 0.65 seconds, or 650 milliseconds. Therefore we can create a constant holding this value, ANIM_TIMEOUT, and pass it to the timeout so that the “switch trick” happens at the exact moment the CSS transition ends.

Test the app again: you’ll see that when you swipe the screen, the cube rotates towards the proper face, and then instantly goes back to the “front” face. Now, if you swipe again, you will indeed get the correct face, because you started from the “front” face again!

Here is the “switch trick” in action: whenever a rotation move finishes, we instantly go back to the “front” face to prepare for the next move

Improving the overall layout

This is already a lot of information on the project goals, how to set it up and how to prepare the basic elements and logic, so let’s stop here for now in terms of purely app-related coding. To catch our breath, let’s do some improvements to our HTML and CSS in order to add a header and a footer, and so that the cube is better showcased in the page.

First, we’ll change our HTML a bit to have three “root” sections in our container – the header, the 3d scene and the footer:

[snippet slug=201121_caliscope-tuto_07 lang=html]

Now, let’s add some styling to our CSS. Apart from the customization of individual elements, this stylesheet now takes advantage of the CSS grid layout for the .container main element to get a vertical column with the header, the scene and the footer on top of each other, where the scene grows and takes up as much space as it can:

[snippet slug=201121_caliscope-tuto_08 lang=css]

This snippet also shows how we can use the “vw” and “vh” (for “viewport width”, “viewport height”) units to get elements response to the current layout size of the user’s browser.

Note: if you haven’t had the chance to discover the incredible power of the grid layout, I encourage you to check out this video with U. Kravets that shows 10 amazing things you can do with “grid” and “flex” layouts, the latest and trendy layout modes in CSS nowadays 😉

Still, if you look at the app on your mobile, you might be disappointed: the top bar seems to have disappeared! This is because the navbar of mobiles (above the browser page) introduces an offset. Also, our cube seems pretty small on this mobile screen! To solve this problem, you might think you should use CSS media queries to apply a specific styling depending on the screen size. However, in our case, it’s even simpler than that: by adding a few meta data in our index.html we can set the proper scale for our layout and immediately get back our layout on mobile.

Both these issues can be counteracted by added a <meta> tag and a little Javascript before our style import in the HTML file <head>:

[snippet slug=201030_caliscope-tuto_09 lang=html]

Next time

Pfiou, that was a lot! At the end of this first part, we’ve managed to setup our project for local development on computer and on mobile, we’ve prepared our layout and we’ve started playing around with the core mechanics of our app – swiping and rotating the cube. Since we use a “switch trick” to always end up on the “front” face of the cube, we don’t need to do any complex calculations for which face we need to land on… and we can even completely remove the “back” face (I’ll leave that as an exercise – remember to clean up both the HTML and CSS code 😉 ).

At the end of this Part 1 of the tutorial, here is what we have:

 

Next week, the second article of this series will focus on loading actual images on our cube, creating a history for our moves so we can “undo” and “redo” moves and even adding an “auto play mode”.

Until then, feel free to react to this post – you can do so in the comments down below or on my LinkedIn 🙂

References
  1. Javascript MDN’s documentation: https://developer.mozilla.org/en-US/
  2. W3Schools’s website: https://www.w3schools.com/
  3. OpenClassrooms’s website: https://openclassrooms.com/en/
  4. “10 modern layouts in 1 line of CSS”, U. Kravets (Google Chrome Developers): https://www.youtube.com/watch?v=qm0IfG1GyZU, July 2020
  5. AnvilEight, “Simple Python HTTP(S) Server — Example” (https://blog.anvileight.com/posts/simple-python-http-server/), March 2016. [Online; last access 21-November-2020].
  6. J. Orendorff, “ES6 In Depth: Arrow functions” (https://hacks.mozilla.org/2015/06/es6-in-depth-arrow-functions/), June 2015. [Online; last access 21-November-2020].
  7.  A. Laxmi and M. A. Perna, “CSS Viewport Units: A Quick Start” (https://www.sitepoint.com/css-viewport-units-quick-start/), June 2020. [Online; last access 21-November-2020].

Leave a Reply

Your email address will not be published. Required fields are marked *