Enhancing E commerce experiences with 3D product configuators
Product configurators have always been popular with high ticket items like luxury cars. Companies like BMW have had "build your own" sections featured prominently in their navigation for years. When people are paying a lot of money they expect a hight degree of control over what they're getting. Even as far back as 2012 a configurator was considered by users as one of the most imortant features of a car company website.
But as customers want more and more personalization out of the products they buy on line, customizable products are becoming the norm outside of just high end markets. Large brands, like Nike allow users to build their own custom shoes; to drive customer engagement, and give people a better sense of personal attachment to their products.
Project Overview
In this article I'm going to demonstrate how we can spice up mundane product options and make a more rich engaging experience for customers. What we are going to build draws inspiration from one of my favorite product builders. The one used by Microsoft's Xbox design lab. This example uses in browser 3D graphics to turn what can normally be a confusing multi page user experience into a simple and elegant set of options.
Using 3D provide a fun and engaging way for customers to interact with the product as well as a few additional benefits like forgoing the need to hire photographers and have expensive photo shoots to show every possible option a product can be purchased in. To do this we are going to use the same open source 3D library used, and supported by Microsoft.
Because 3D models can be rendered dynamically we can render our product in any color or configuration we want with one 3D asset. In this case I fount a free 3D model on line from free3d.com. Our fictitious store is going to be an on line bike shop. And this 3D road bike is going to be our fictitious product. What we are going to be building is included below.
The file format we want here is a .obj file, sometimes called a WaveFront model. This is a standard format for 3D models used to transfer a model from one 3D software package to another. We can then use Blender, a free open source 3D modeling and image editing software package. To prepare the model to be loaded on the web. This article isn't going to cover how to use Blender because that is a huge topic in and of itself. You can find excellent tutorials on blenders website. For the sake of this tutorial I have included an already exported 3D model in the github repo, called "bike.babylon". But if you want to use your own model here is instructions for exporting from blender for the web.
There are a few different tools out there for doing 3D graphics in a web browser.The one we will be using is called Babylon.js. Babylon is maintained by Microsoft and, in my opinion the most feature rich and easy to use of all JavaScript 3D engines.
Canvas and WebGL
Babylon is made possible by an HTML5 browsers API called webGL. WebGL is a subset of the OpenGL ES standard for computer graphics used on native mobile and desktop devices. The webGL API allows web developers to execute code on users graphics cards directly. This is where the computing horsepower needed for dynamic 3D rendering comes from. While the webGL API is powerful, it is a very low level API and is extremely technical to work with. It requires a good working knowledge of linear algebra.
Babylon will abstract all this away for us and make working with 3D in the browser much easier. We will be able to get away with just knowing some simple geometry. It will however be helpful to have a good knowledge of 3D terminology and some experience modeling in software like Blender, Maya, or 3DS Max.
We are also going to be using React.js to handle the UI in this demo. No prior knowlege of react is required but experience in it will certainly be helpful. Lets start with a short primer on both React and Babylon to go over some typical conventions of both libraries.
An introduction to Babylon.js
Babylon allows us to create a 3D scene and load and interact with models with an intuitive object oriented API. In fact, Babylon has a more object oriented approach than is typical with most JavaScript libraries. Working with Babylon tends to feel a lot like working with iOS graphics libraries.
When working with Babylon everything added to our scene is an instance of one of the objects Babylon provides. Models are instances of the Mesh object, which is made up of a Geometry object and a Material Object. The Geometry is the skeleton of the model and the material contains information like what color it should be. A typical Babylon object looks like this:
You can find documentation for all of Babylons classes on their docs page. We will explore most of the more common classes as we go through this tutorial.
An introduction to React.js
React is a front end JavaScript UI library created by Facebook. It has quickly become the dominant front end library used today for building rich web applications. React is essentially a tool for managing UI state. It allows us to declaratively specify what our UI should look like and then let React take care of updating the data for us as it changes.
It also allows us to break our application down into small reusable parts called components. A component is basically your own custom HTML tag you can use anywhere in your application. Check out Reacts getting started page for more information, but here is what a basic component will look like.
{this.props.greeting}
) } } export default GreetingThis class will export a component that can be used like so.
The first thing you'll probably notice is the very odd chunk of HTML in a JavaScript method. This is called JSX and it is a special file format created along with React to make it easier to describe your UI in JavaScript. When building React applications you have a build script that parses these JSX tags into JavaScript function calls that actually render the UI in browser.
Its definitely a different way of thinking about it compared to traditional web development. It seems pretty awkward at first but eventually you'll come to appreciate having all the power of JavaScript when building your layouts.
The "props" property of a component is anything passed down to it from a parent. This way you can pass data the same way you would use HTML attributes. For internal data React has a property known as state. State is data set on a component that will be automatically updated in the UI when it is modified. State in react looks like this.
{this.props.greeting} {this.state.name}
The preceding code will update our name value and re render the h1 tag any time the input is typed into. Management of state is really the core of how React works. Knowing this is knowing most of React. There are a few other necessary things to learn about it but we will be covering those as we go through the project.
You might be wondering if we really need to be using React for this demo, and the answer is probably no. Our UI is probably simple enough that we could get by without it. But if we wanted to expand the scope of our UI in the future react would help us keep a well organized and east to maintain project. Building a tutorial where React is really necessary can be tough because it really shows its benefits in large projects that would be pretty difficult to do a full text write up on. Especially one captivating enough to keep peoples attention through the whole thing. So I think this is a good project to introduce the concepts with.
Starting our product configurator
To start off we will need to download the create-react-app cli we can do this through the npm package manager. In terminal type
yarn global add create-react-app
Now create a new project directory and type
create-react-app product-3D
cd product-3D
You should end up with a folder structure that looks like this
node_modules/ public/ --favicon.ico --index.html --manifest.json src/ --App.css --index.js --App.js --logo.svg --App.test.js --registerServiceWorker.js --index.css .gitignore README.md package.json yarn.lock
You can learn more about the create react app boilerplate from their github page. But all these files are generated by it to set you up with a basic template to start from. This configures babel and web-pack to run for you so you don't have to worry about any set up and can just start writing code.
Next we need to install our dependencies. We already have React. We can get everything else we need with:
yarn add babylonjs gsap
Most of our work will be done in the src/ folder. To start we will create a component to render our 3D scene. Create a Components directory in the src/ folder and create a Scene3d.js
The Render Function
The above component will hold all our 3D content which is loaded in an HTML5 canvas tag. The render function will simply return our canvas tag. The "rel" attribute alows us to pass a function that recives the DOM element which we can use to save as a property on our component class. This lets us bypass the need to use something like Jquery to select the element. Then we can access this as "this.stage" in our class methods.
This reference is what we will use to pass the canvas element to Babylon.js. This happens in the "setEngine" method. The engine class takes three arguments. The canvas element - in our case "this.stage", a boolean that says if we want to use webGL vs the 2D canvas API, and another boolean that says if we want anti-aliasing - which makes the edges of our model look nicer but lowers performance a bit. In our case we want both booleans to be set to true.
Creating our 3D scene
After the engine the next piece we need for any 3D scene will be the actual Babylon scene object. This object is a manager for all the other objects in our scene. This pattern is commonly refereed to as a scene graph. It hold 3D object classes much in the same way the DOM object holds the HTML elements of a web page. If we want an object to appear in our 3D scene we add it to the scene object.
Adding a camera
The next important step our scene needs to render is a camera. In Babylon and most 3D renderer s we have to add a camera before anything will show up in our scene. The camera acts at the view port the user looks through. The camera takes the following arguments: an id for selecting it in the scene if we need to, the alpha, delta, radius, a point to look at, and the scene to add the camera to. Babylon.js has quite a few built in camera types, the one we are using in this example is the Arc Rotate Camera. It is a special camera designed for rotating around a model. It is an excellent camera type to use for things like product viewers. The camera is essentially fixed to a sphere around the point it is set to look at.
The most important parameters in the camera are the alpha, beta, and radius. Think of this as the cameras x, y, and z coordinates. The alpha is the left and right position of the camera. The beta is the up and down position and the radius is the zoom level of the camera. It can be tricky to think in this way at first but it's fairly simple once your get used to it.
We move the focal point back a bit on the y and z plane to make the camera appear to focus on the center of the bike model.
Putting it all together
Now that we have all the basic elements of a scene its time to put them all together and make something happen. We will add the following code in a react specific function called "componentDidMount". This method is part of Reacts built in life cycle methods and is called whenever a component is fully loaded into the DOM. We use this specific method because that this point we know our canvas tag is loaded and ready to be used. In this method will will make the calls to all our other set up methods in the order they are needed so Engine > Scene > Camera.
After this our basic Babylon scene component is ready to go so we can go to the parent App component that create-react-app provided for us and load it.
Loading our Model
If we come back and check out browser now you'll notice we have... nothing. Kinda disappointing I know, but we actually do have a working 3d scene here; we just haven't started it yet. We have to load our model first. Lets add a loadModels function that will be responsible for fetching our model.
The first step, if you haven't already is to actually go get the model. It can be found in the public folder of the project directory on github. The file is called "bike.babylon". Or, if you prepared your own model you can use that.
Pay special attention to the runRenderLoop method. This takes a call back that is going to be called over and over again. To be precise, it will try to run 60 times per second - if possible. Inside this callback we call the render method on the scene, this draws a frame in our 3D scene. So each time it runs, up to 60 times per second it will draw a frame. This is what causes the scene to appear animated. By redrawing a new frame of every small change in the scene it creates a flip book style effect that gives the illusion of motion.
Then we can call the loader in our componentDidMount method.
Now if you refresh your browser you should see our bike model right in the middle of your screen. You'll also notice that if you click and drag around with your mouse the scene already has a nice level of interactivity built in. This is one of the benefits that Babylon.js's ArcRotateCamera gives us. This is a nice advantage Babylon has over other 3D Libraries. The built in controls already contain some nice smooth physics effects that would be a lot of work to implement ourselves.
Adding accessible UI controls
With our model loaded and controls working, the next thing to do is implement some of our product configuration controls. This is the part where React comes in as We are going to use regular DOM elements overlay-ed on top of the canvas for this. While you can add click able control elements inside a Babylon scene - for most apps - it really isn't worth it to do so. Native DOM elements have years of testing and development behind them in modern browsers and so we are going to have a much smoother experience implementing our controls that way. Native DOM elements give us efficient click event listeners and the ability for users with disability's who cannot use a mouse to use them with the keyboard.
Lets start by creating a component to hold out controls. We create an individual control component that makes up each individual section we want a control for. Create a Controls.js file, and a ProductOptionControls.js file in the components directory.
src/Components/Controls.js
src/Components/ProductOptionControls.js
This is as complex as this component will need to get. It takes in a list of props, maps over those props, and returns a component for each one. Mapping arrays is a common operation in many programing languages. A map function takes an array and calls a function on every element in the array, creating a new array from the returned result of each function call. In JavaScript map is a method of the built in array object. Because JSX is compiled to JavaScript function calls in React we can use JSX directly as if it where JavaScript, which is why we can return it directly from our map function. The end result is an array of our elements to render to the screen.
We can set up our individual controls in our ProductOptionControls component. Each control is going to be in an accordion so this component will handle that along with all the various click events we need to set up.
This gives us our basic structure now we need to load these components in app.js and pass in the options we want. We will set the options as state on the app component. We have one array to hold the option names and another multidimensional array to list out the possible options that can be selected.
We have to pass these options down through our controls component. In Controls.js we will pass each configuration option to a ProductOptionControl component as props inside our map function. You will notice we are also using a special prop called key, this is just a unique identifier that helps React identify which component is which for updates. All you need to know about this is that it has to be unique to each item and is generally added any time you create a list of the same component in a loop.
Styling our layout
Since this is not a CSS tutorial I am not going to go into detail on the styling of the layout. The CSS is very simple and I am including the contents of index.css below. This should be all you need to get the layout looking the way it does in the final example.
You should now see what makes up the bulk of our layout. We have our 3D scene with an overlay of an accordion of controls on the right hand side. From here all we have to is to actually link the controls up and make them functional.
Communicating between components
When you break the elements of your page down into components like this you are inevitably going to have to deal with the issue of having components communicate with one another. In our case we need our controls to be able to communicate with our scene. Our controls need to be able to change the state of the scene. There are well established patterns for managing state in React. While its good to be aware of these things and when you may need them our example is simple enough that we can get by with a simple event driven design.
Lucky for us modern web browsers ship with an extensible and robust event system built in. We use the browsers event system anytime we listen for clicks on buttons or links. We can use this built in event system to transmit our own events.
We do this by creating our event as an instance of the CustomEvent class provided by the browser. The constructor for a CustomEvent takes two arguments. The name of the event - this can be any string you want to name it and will be how the event is hooked into by the listener - and an options object where we can pass custom data to our listener via the detail property. We will use the detail property to pass the name of our control section, so we know which control this is coming from. Then we can call the dispatchEvent method on the window object and pass it our CustomEvent to actually trigger the event.
Next we need to listen for that event in our Scene3d component. When a "move-camera" event is fired we are going to animate our 3D camera in our scene to focus in on the section of the model affected by those controls.
To do that we need to know where the camera needs to be according to the cameras alpha, beta, and radius properties. This can be a tough thing to visualize in our heads so it is best to actually use the built in scene controls to find the best position. To do this we simply need to do a console log one the camera inside our render loop. This way we will get real time updates as to where the camera is as we move it. Then we just move around with the mouse until we find a position we like and we save that inside an object in our component. I have included the coordinates I have chosen in the example below but you can position these however you like.
Animations in Babylon
You'll notice we are using an additional library here in our move camera function. TweenMax is part of the GSAP JavaScript animation library . It is an extremely common utility used in web animation. Babylon does include its own animation libraries but they are a bit more verbose and limited to animating the elements inside a Babylon scene. TweenMax is a bit more flexible as it can animate basically anything, so we can use one animation API to animate our 3D objects as well as DOM elements like our control accordions.
While TweenMax is extremely flexible and powerful 90% of the time you will never need more than the basic `to` method, which takes an object to animate, a time duration in seconds, and an object of properties to animate the pain object to. In our case we are animating the alpha, beta, and radius of our Babylon.js camera.
This code is all it takes to wire up our first basic animation, now when you click on the contol labels you should automatically be taken to the area of our model we are going to be manipulating. Next We need to wire up our controls inside the acordion to be able to modify the colors of different sections of our model.
This code is basically the same thing we did before to fire off our own custom event from the ProductOptionControl component. Next we will add another listener to out scene3D file along with a hash to transform color values from text to the Color3 rgb class that Babylon use internally.
Working with materials
The color of a 3D object is defined by what is refereed to in the 3D world as a material. A material consist of much more than just color actually. It controls things like how the object reacts to light. Materials are how we create photo realistic metal sheens or transparent glass effects.
In our example Babylon handles the material for us, actually it was already attached to our model when we imported it. We only need to access it as a property of our Mesh. We can uses Babylons "getMeshByID" method on the scene to select a particular mesh. This model has hundreds of meshes so rather than click through those in the JavaScript console looking for the right one, it is easiest to just go back it to Blender and right click on the part of the mesh we are looking for. Then you will see the object highlighted on the right in blenders object graph. The name here will also be the ID of the mesh. If you're using the provided 3D model you can just use the Id names in my example.
In the code above I am modifying the colors a bit when the mesh is loaded to match up with the options I'm making available to users. Notice that I am making a clone of the material then reassigning it before changing the color. This is an important step that if we miss will cause strange bugs down the road.
In Babylon; and most 3D renderer's, it is possible and often preferred to share one instance of a material across many meshes. This is more efficient but comes at a cost in that we can not modify the color of one piece without modifying the color of all the other meshes who share that material.
To get around this we simply duplicate the current material and apply a new instance to the items we wish to manipulate. This keeps all the material properties isolated to that one mesh. Now if we click through our controls we should have our camera animations and our color changes. Pretty cool right, and it isn't even that much code especially considering what you would have to do to do this in raw webGL. Next we will focus on putting some finishing touches on our scene to make it more refined.
Finishing Touches
We need to set some sensible limits on how much the user can rotate around our model. This is because of our animations. If we let the user rotate a hundred times around the model then when it animates the camera back to one of our fixed positions it will spin a hundred times back like some sort of awkward wind up toy. You could also fix this by normalizing the rotations but it is easier to just fix everything to one rotation.
Next we want to make sure our interface behaves just as nicely for people who are unable to use a mouse as it does for those who are using one. We already have a decent start at this using Babylon as its built in camera controls can be used with the arrow keys by default as well. You can also hold option + up or down arrow to zoom in and out. Because we used native HTML buttons for our controls the can be used nicely by tabbing through them and hitting the enter key. However, our accordions are making things kind of awkward because it will tab through the hidden options of the closed accordions. This is not going to be a good experience for those users as they cannot see what the options are. We want the internal accordion buttons to only be tab-able if the accordion is open. We can do this by setting the CSS visibility on the accordion body. Once it is closed we need to make sure the visibility is set to hidden and then just before it opens again we set our visibility back to visible so we can still see the animation.
We will also add another event to make sure that when one accordion is opened the other ones close automatically. This will make for a smoother user experience.
The last small finishing touch I added was put our company logo in as the floor of the scene. Im not going to go into to much detail on this since this tutorial is long enough as as is. However, I will add detailed comments to the code to do this so you can see a little bit more about how to work with images and textures in Babylon.
That wraps up our tutorial on building a 3D product display with React and Babylon. If you had the patience to make it this far then kudos to you I know this was a pretty lengthy write up. My recomendations for extras you could impliment yourself are;
- Checkboxes that show / hide certain meshes to add / remove features. Like the extra water bottle.
- Highlighting the different meshes you can edit when rolling over the model. You can use Babylon's "renderOutline" property on a mesh for this.
- Open the accordions based on clicking that part of the model. You can use Babylon's ActionManager class for this.
If you had any issues following along add them as issues on the github repo the project is hosted at. Or if your way smarter than me about something and want to point out something I did wrong, or could have done better be sure to point that out as well. Thanks for reading!