Enhancing E-commerce Experiences with 3D Product ConfiguratorsProduct 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. If you're looking to take your e-commerce site to the next level, here's a great way to present your products and communicate quality and prestige to your customers.
Project OverviewIn 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 full source code can be found here.
Canvas and WebGLBabylon 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.
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.
This class will export a component that can be used like so.
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 configuratorTo 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-appNow create a new project directory and type
cd product-3DYou 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.lockYou 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 gsapMost 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 FunctionThe 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 sceneAfter 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 cameraThe 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 togetherNow 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 ModelIf 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 controlsWith 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.
This gives us our basic structure now we need to load these components in app.js and pass in the options we want. Set the options as a state on the app component, and use 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 layoutSince 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 componentsWhen 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.Modern web browsers ship with an extensive built in event system. Usually it is used to listen for button or link clicks. We can use this system to transmit our own events.First, create an event as an instance of the browser CustomEvent class. 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.
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.
I am modifying the colors when the mesh is loaded to match up with the user options I'm making available. 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 TouchesWe 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'll make sure our interface behaves as nicely for people who are unable to use a mouse as it does for those who can. Babylon's built in camera controls can be used with the arrow keys by default. You can also hold option + up or down arrow to zoom in and out. Tab through native HTML buttons and hit enter for controls. However, the accordions are making things awkward by tabbing through hidden options. Not being able to see options means a bad user experience. Internal accordion buttons should only be tab-able if the accordion is open. Set the CSS visibility on the accordion body. Then, make sure the visibility is set to hidden and switching it back just before it opens again to see the animation. To make things even more smooth, add another event to close an accordion when another opens.
Placing the company logo on the floor is the finishing touch. Im not going to go into to much detail on this since this tutorial is long enough as as is. However, I'll add detailed comments to the code to illustrate more about working 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 recommendations for extras you could implement 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.