Introduction
For a project at work, I needed to learn some simple 3D CSS techniques. So, I decided as a side project to build an Xbox Series X and S model in CSS to learn how to accomplish this.
In this small demo you can choose between both Xbox Series models. When you select the unselected model a transition occurs. Moving your cursor to either side of the model will slightly change the view.
You can find the demo here (looks much better in its full glory here):
https://codepen.io/tumain/full/poyqVeb
I thought it would be interesting to cover how I created the demo and some of its visual aspects; including:
- Cube creation
- Transitions between models
- Patterns on models
- Xbox logo creation
- 3D movement on cursor position
Cube creation
To begin, I looked at how to create a cube. I used this guide to construct one. I recommend reading the article if you're unfamilar with CSS cube creation, but in short; I created a .cube
class. This .cube
class has six elements within, each representing a face of the cube: top, right, bottom, left, back, front. Altering the translation and rotation of each cube face via 3D transforms, allowed me to create a cube.
I then recorded the dimensions of each Xbox and sampled each of their colours from the image below.
Each model would share the same CSS variable that would be changed upon transition; so I created CSS variables to store this data.
:root {
--height: 55vw;
--width: 30.2vw;
--depth: 12.6vw;
--seriess: #E7E7E7; // Series S background colour
--seriesx: #1F1E25; // Series X background colour
--view: -222deg; // View of the scene
}
I updated the existing CSS widths and heights I used from the guide, to use these CSS variables. I then tweaked the 3D transform of the model until I was satisfied with the view.
Transitions between both models
Loaded with the colours and dimensions of each model, I created two classes, putting the colours per face of the Xbox (box) model. One for .series-s
and the other for .series-x
. This class is applied to the body
tag, depending which model is selected.
The next part of the puzzle was to update the CSS variables depending on which model was selected. I executed this by using JS' style.setProperty
method. For example, if I wanted to change the width (--width
) of the model on screen I would do this:
document.documentElement.style.setProperty("--width", NEW_WIDTH_HERE + "vw");
In my JS I store an object holding the dimensions of each model. Here's an example of the Series S.
let seriesS = {
height: 55,
width: 30.2,
depth: 12.6
};
I created a function where you can pass in these properties and it updates the dimensions of the model.
let setProperties = (props) => {
document.documentElement.style.setProperty("--width", props.width + "vw");
document.documentElement.style.setProperty("--height", props.height + "vw");
document.documentElement.style.setProperty("--depth", props.depth + "vw");
};
To trigger this I made a clickable X and S element at the bottom of the page. If I wanted the S model, I simply call the setProperties
method with the seriesS
object and remove the current class from the body
tag and add the class I wanted; series-s
.
let seriesSSelected = () => {
setProperties(seriesS);
document.body.classList.add("series-s");
document.body.classList.remove("series-x");
};
After getting the transition between colours and size working, I added the visual elements of each model in.
On the S model there is a large black circular vent using the class .circle
. This is simply a black circle (border-radius: 50%
) absolutely positioned on the front face of the model.
To achieve the transition where it shrinks when the X model is selected I created a x-scale-0
class. This class is a child of .series-x
and simply sets the scale of the element to 0. So when the Series X is selected the scale down occurs.
.series-x .x-scale-0 {
transform: scale(0);
}
Likewise there is a .s-scale-0
class, which works the other way.
Patterns on models
At the top of the S and X models there are circular vents. There are also circular vents on the S' model front. To achieve this pattern I used a background; utilising radial-gradient
and background-size
.
The following is used for the S' front circles.
background-size: .9vw .9vw;
background-image: radial-gradient(#000 50%, transparent 50%);
I tweaked the background size to increase/decrease the size of the circles, depending on the scenario.
Xbox logo creation
The Xbox logo is made up of three circles:
- Perfectly round circle, used for the background
- Nested in 1; a transparent shape with a border applied and differing width and heights
- Same as 2 but placed in a different position
For points 2 and 3, I tweaked the width and height a lot to get the desired outcome.
3D movement on cursor position
I added this at the last minute just to show off that it is 3D. This uses the CSS variable --view
, that we mentioned at the start.
I firstly added event listeners onto the body, tracking mousemove
and mouseleave
. mousemove
slightly changes the CSS --view
variable depending on the cursor position; whereas mouseleave
resets the --view
to its initial variable.
// the scene's initial rotation value
let initialView = -222;
// move rotation on mouse movement
let onMouseMove = (e) => {
// calculate percentage of the cursor's x position
// e.pageX: cursor position
// window.innerWidth: screen width
xPercent = parseInt((e.pageX / window.innerWidth) * 100) - 75;
// add the movement to the initial view
var view = initialView;
view += xPercent / 2;
// update the --view CSS variable
document.documentElement.style.setProperty("--view", view + "deg");
};
Hopefully the commented code above makes sense. The value '75' was used because it felt like a healthy offset to move the camera to the left or right.
The mouse leave event just resets the model to its initial view, so when the cursor goes off the screen the view resets.
let onMouseLeave = (e) => {
document.documentElement.style.setProperty("--view", initialView + "deg");
};
Then we also need to add the event listeners.
let b = document.body;
b.addEventListener("mousemove", onMouseMove);
b.addEventListener("mouseleave", onMouseLeave);
Conclusion
And there you have it. Hopefully me going into the depths of how the scene was developed has been an interesting read. Whilst it isn't the most complex of scenes, I thought it would be useful to go into the detail of how I created some of these visual effects, since this was the first time using 3D CSS.
Thanks for reading.
Top comments (17)
Gorgeous!
Thanks Ben.
Looks awesome!, just one question: Why use css instead of canvas?
To be more complex than a simple canvas
I did a little stuff with BabylonJS whilst learning 3D, but continued to work with just CSS in the end for this demo. I just found it quicker to achieve what I wanted.
Excellent
Amazing!
do you have any resources/articles on making 3d Faces and allowing them to move along with the move of Cursor?
Sorry I haven't. When you say faces do you mean a side of a 3d element or do mean an actual face that moves it's eyes to the cursor?
yeah somewhat like that.when a cursor moves the face and eyes also moves
Wow impressive should we take this as a sign that you bought the new Xbox and not the Playstation 5?
Haha. Neither yet. Waiting for some exclusives before making an initial decision.
Wow!
Nice job ๐
That is awesome : )
Awesome !
Very nice!