Build a 360 view image slider with JavaScript

Build a 360 view image slider with JavaScript

  • Knowledge needed: Basic HTML, intermediate jQuery, JavaScript and CSS
  • Requires: jQuery, CanvasLoader, pre-rendered image sequence
  • Project time: 1 hour

Robert Pataki of Waste Creative demonstrates how to show-off great looking products and keep users interested by making your own 360 view image slider in JavaScript

This article first appeared in issue 224 of .net magazine the world's best-selling magazine for web designers and developers.

We’re going to build a 360 view image slider. What can you use it for? Well, it comes in very handy when your client wants to show their product from every angle rather than showing just a couple of simple angle shots.

We’ve seen quite a few nice interactive examples from car and phone manufacturers showcasing their products using this technique, but in most cases they use Flash.

I love interactivity, but I don’t really like using plug-ins unless it’s essential, so in this tutorial we’ll build our own 360 view application using HTML, CSS and JavaScript.

How will it work?

We’re going to be using a pre-rendered image sequence, displaying a 3D object rotated around its y-axis. The sequence contains 180 images to show an animation as smoothly as possible. The user can ‘rotate the object’ by dragging the mouse horizontally, or by swiping their fingers over the image slider on a touchscreen.

How will we do it?

First we’ll build our HTML markup, then we’ll add some custom CSS including media queries, then we’ll make it groovy with JavaScript. To make it more exciting not only will we make it work on desktop computers but also on iPhones and iPads because the app works very well with touch events.

Before we start, let me say a massive thank you to Mateusz Sypien and Maya Prodanova for their amazing 360 artwork!

1. Folders

In the project folder we have a css folder, a js folder and an img folder. The css folder contains the reset.css file, the img folder contains the image sequence and the js folder contains the jQuery and Heartcode CanvasLoader libraries.

 2. New project

Create a new HTML file and save it to the project root as index.html. In the <head> we set the viewport for mobile devices by making our content non- scaleable. We include two CSS files: Eric Meyer’s reset.css and the threesixty.css, which will contain our custom styles.

  1. <!DOCTYPE html>
  2. <html lang="en">
  3.         <meta charset="utf-8">
  4.         <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0" />
  5.         <title>360</title>
  6.         <link rel="stylesheet" href="css/reset.css" media="screen" type="text/css" />
  7.         <link rel="stylesheet" href="css/threesixty.css" media="screen" type="text/css" />
  8. </head>
  9.  
  10. </body>
  11. </html>

3. Loading percentage

Create a wrapper <div> for the slider. It contains an <ol>, which will hold the image sequence as <li>s, and also holds the preloader <div> holding a <span> for the loading percentage display. We will add the images dynamically using JavaScript.

  1. <!DOCTYPE html>
  2. <html lang="en">
  3.         <meta charset="utf-8">
  4.         <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0" />
  5.         <title>360</title>
  6.         <link rel="stylesheet" href="css/reset.css" media="screen" type="text/css" />
  7.         <link rel="stylesheet" href="css/threesixty.css" media="screen" type="text/css" />
  8. </head>
  9.         <div id="threesixty">
  10.                 <div id="spinner">
  11.                         <span>0%</span>
  12.                 </div>
  13.                 <ol id="threesixty_images"></ol>
  14.         </div>
  15. </body>
  16. </html>

4. Adding interaction

Just before the </body>, include the JS files we’ll be using. jQuery helps us to add interaction quickly, the Heartcode CanvasLoader will add a smooth preloader animation. The threesixty.js file will contain the JavaScript that controls the image slider.

  1. <!DOCTYPE html>
  2. <html lang="en">
  3.         <meta charset="utf-8">
  4.         <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0" />
  5.         <title>360</title>
  6.         <link rel="stylesheet" href="css/reset.css" media="screen" type="text/css" />
  7.         <link rel="stylesheet" href="css/threesixty.css" media="screen" type="text/css" />
  8. </head>
  9.         <div id="threesixty">
  10.                 <div id="spinner">
  11.                         <span>0%</span>
  12.                 </div>
  13.                 <ol id="threesixty_images"></ol>
  14.         </div>
  15.        
  16.         <script src="js/heartcode-canvasloader-min.js"></script>
  17.         <script src="js/jquery-1.7.min.js"></script>
  18.         <script src="js/threesixty.js"></script>
  19. </body>
  20. </html>

5. Styling

Now create the threesixty.css file. The reset.css file sets all the default behaviours, so we can move on to the fun bits. Style the #threesixty wrapper first. The default image slider will be 960px by 540px, centred horizontally and vertically. We also set the <ol> element to be hidden.

  1. #threesixty {
  2.         position:absolute;
  3.         overflow:hidden;
  4.         top:50%;
  5.         left:50%;
  6.         width:960px;
  7.         height:540px;
  8.         margin-left:-480px;
  9.         margin-top:-270px;
  10. }
  11. #threesixty_images {
  12.         display: none;
  13. }

6. Set the display

To make the interface work on different screens while keeping proportions consistent, we’re going to control the display using media queries. You can see in the snippet above how we define our different resolution and orientation device criteria using the max-device-width and orientation properties combined with the and operator. The snippet above sets the display for iPad (1024px wide) and iPhone/iPod touchscreens, both in landscape and portrait modes (480px wide). The images will take up all of the available space inside the wrapper.

  1. @media  screen and (max-device-width: 1024px) and (orientation:portrait) {
  2.         #threesixty {
  3.                 width:720px;
  4.                 height:450px;
  5.                 margin-left:-360px;
  6.                 margin-top:-225px;
  7.         }
  8. }
  9. @media  screen and (max-device-width: 480px) and (orientation:landscape),
  10.                                 screen and (-webkit-min-device-pixel-ratio: 2) and (orientation:landscape) {
  11.         #threesixty {
  12.                 width:360px;
  13.                 height:225px;
  14.                 margin-left:-180px;
  15.                 margin-top:-113px;
  16.         }
  17. }
  18. @media  screen and (max-device-width: 480px) and (orientation:portrait),
  19.                                 screen and (-webkit-min-device-pixel-ratio: 2) and (orientation:portrait) {
  20.         #threesixty {
  21.                 width:320px;
  22.                 height:200px;
  23.                 margin-left:-160px;
  24.                 margin-top:-100px;
  25.         }
  26. }

7. Images

All the images will be placed in the <ol>. First we set the style for every image in the wrapper. We don’t want the images to all be visible, so we define the current-image and the previous-image classes that control their visibility. We’ll swap between these states with JavaScript.

  1. #threesixty img {
  2.         position:absolute;
  3.         top:0;
  4.         width:100%;
  5.         height:auto;
  6. }
  7. .current-image {
  8.         visibility:visible;
  9.         width:100%;
  10. }
  11. .previous-image {
  12.         visibility:hidden;
  13.         width:0;
  14. }

8. Preloader style

Style our preloader by making #spinner hidden, setting its dimensions and placing it in the centre of the wrapper. We also set the styles of the span inside the #spinner to be horizontally and vertically centred so the text will be in the middle of the circular animation.

  1. #spinner {
  2.         position:absolute;
  3.         left:50%;
  4.         top:50%;
  5.         width:90px;
  6.         height:90px;
  7.         margin-left:-45px;
  8.         margin-top:-50px;
  9.         display:none;
  10. }
  11. #spinner span {
  12.         position:absolute;
  13.         top:50%;
  14.         width:100%;
  15.         color:#333;
  16.         font:0.8em Arial, Verdana, sans;
  17.         text-align:center;
  18.         line-height:0.6em;
  19.         margin-top:-0.3em;
  20. }

9. Getting ready

Create a new JS file and save it as threesixty.js in the js folder. Place the code into the jQuery DOM-Ready function, so that by the time the script starts running, the DOM elements are already in place. ready will enable the user interaction when our app is ready.

Dragging tells us if the user is using the pointer. With speedMultiplier we set the speed of the image sliding; we’ve got some variables to store the pointer positions – the timers will track the pointer changes – and we define some variables to keep track of the frame calculations and image loading.

  1. $(document).ready(function () {
  2.         var ready = false,
  3.                         dragging = false,
  4.                         pointerStartPosX = 0,
  5.                         pointerEndPosX = 0,
  6.                         pointerDistance = 0,
  7.  
  8.                         monitorStartTime = 0,
  9.                         monitorInt = 10,
  10.                         ticker = 0,
  11.                         speedMultiplier = 10,
  12.                         spinner,
  13.        
  14.                         totalFrames = 180,
  15.                         currentFrame = 0,
  16.                         frames = [],
  17.                         endFrame = 0,
  18.                         loadedImages = 0;
  19. });

10. Spinner

We create the addSpinner function that adds a CanvasLoader instance with custom settings inside the #spinner. The spinner will be a 90x90px, spiral shaped loader with a smooth circular animation. Call its show method, then display it by using the jQuery fadeIn.

  1. function addSpinner () {
  2.         spinner = new CanvasLoader("spinner");
  3.         spinner.setShape("spiral");
  4.         spinner.setDiameter(90);
  5.         spinner.setDensity(90);
  6.         spinner.setRange(1);
  7.         spinner.setSpeed(4);
  8.         spinner.setColor("#333333");
  9.         spinner.show();
  10.         $("#spinner").fadeIn("slow");
  11. };

11. Image loading and frames array

The load image function creates a <li> with an <img> inside. Hide the image with the previous-image class. The loadedImages variable generates the image name, which increments each time a new image is loaded; if successful, we call the imageLoaded function.

In one single line of code we create a new <img>, point its source to the generated file name, then hide it by applying the previous-image class. All in one line thanks to jQuery! We store each image object returned by jQuery in the frames array, which will be handy when it comes to animation.

  1. function loadImage() {
  2.         var li = document.createElement("li");
  3.         var imageName = "img/threesixty_" + (loadedImages + 1) + ".jpg";
  4.         var image = $('<img>').attr('src', imageName).addClass("previous-image").appendTo(li);
  5.         frames.push(image);
  6.         $("#threesixty_images").append(li);
  7.         $(image).load(function() {
  8.                 imageLoaded();
  9.         });
  10. };

12. Image overload

There are too many images to load all at once, so we call loadImage recursively. The image loading process is shown by writing the percentage text into the #spinner <span>. Once all the images are loaded, we make the first image visible and hide the preloader.

  1. function imageLoaded() {
  2.         loadedImages++;
  3.         $("#spinner span").text(Math.floor(loadedImages / totalFrames * 100) + "%");
  4.         if (loadedImages == totalFrames) {
  5.                 frames[0].removeClass("previous-image").addClass("current-image");
  6.                 $("#spinner").fadeOut("slow", function(){
  7.                         spinner.hide();
  8.                         showThreesixty();
  9.                 });
  10.         } else {
  11.                 loadImage();
  12.         }
  13. };

13. Smooth transition

Display the image slider with a smooth transition using the showThreesixty function. We launch the app by calling addSpinner() and loadImage(). When tested, the preloader fades in, and when the images loaded, a transition hides it and displays the first image.

  1. function imageLoaded() {
  2.         loadedImages++;
  3.         $("#spinner span").text(Math.floor(loadedImages / totalFrames * 100) + "%");
  4.         if (loadedImages == totalFrames) {
  5.                 frames[0].removeClass("previous-image").addClass("current-image");
  6.                 $("#spinner").fadeOut("slow", function(){
  7.                         spinner.hide();
  8.                         showThreesixty();
  9.                 });
  10.         } else {
  11.                 loadImage();
  12.         }
  13. };
  14.  
  15. function showThreesixty () {
  16.         $("#threesixty_images").fadeIn("slow");
  17.         ready = true;
  18. };
  19.  
  20. addSpinner();
  21. loadImage();

14. Frame values

The slider animation is going to be simple: we tween the current frame value to a set end frame value and display the current image. A custom easing method calculates the distance between the frames and creates a smooth spinning animation in either direction.

Create the render function that updates the frame animations. If the current frame hasn’t reached the end frame, move it closer by changing its value: when it reaches the end frame, stop the rendering. Update the images with the hidePreviousFrame and showCurrentFrame functions.

  1. function render () {
  2.         if(currentFrame !== endFrame)
  3.         {      
  4.                 var frameEasing = endFrame < currentFrame ? Math.floor((endFrame - currentFrame) * 0.1) : Math.ceil((endFrame - currentFrame) * 0.1);
  5.                 hidePreviousFrame();
  6.                 currentFrame += frameEasing;
  7.                 showCurrentFrame();
  8.         } else {
  9.                 window.clearInterval(ticker);
  10.                 ticker = 0;
  11.         }
  12. };

15. Ticker

The refresh function calls our render function, which creates a setInterval that we store in the ticker variable. To make sure we don’t make any unwanted calls, check to see if the ticker is already running. I set the FPS value to 60, so we can have a nice, smooth animation.

  1. function refresh () {
  2.         if (ticker === 0) {
  3.                 ticker = self.setInterval(render, Math.round(1000 / 60));
  4.         }
  5. };     

16. Normalised value

hidePreviousFrame and showCurrentFrame swap the states of the current image. Call getNormalizedCurrentFrame() to get the frame value between 1-180, which returns the ‘normalised’ value of currentFrame that we use to manipulate the current image.

 To get a ‘swooshy’ effect the current frame value can’t go out of the range defined by totalFrames. With the getNormalizedCurrentFrame function commands, we can calculate the values within the totalFrames range so the animation moves accurately.

  1. function hidePreviousFrame() {
  2.         frames[getNormalizedCurrentFrame()].removeClass("current-image").addClass("previous-image");
  3. };
  4.  
  5. function showCurrentFrame() {
  6.         frames[getNormalizedCurrentFrame()].removeClass("previous-image").addClass("current-image");
  7. };
  8.  
  9. function getNormalizedCurrentFrame() {
  10.         var c = -Math.ceil(currentFrame % totalFrames);
  11.         if (c < 0) c += (totalFrames - 1);
  12.         return c;
  13. };

17. Testing

Let’s give it a quick test. In the showThreesixty function we will add two things: first we set the endFrame to -720, and second, we call the refresh method. If you test the application, you will see the images quickly swooshing around as they’re fading in.

  1. function showThreesixty () {
  2.         $("#threesixty_images").fadeIn("slow");
  3.         ready = true;
  4.         endFrame = -720;
  5.         refresh();
  6. };

18. User interaction

Add the user interaction. Start with the mouse event listeners, then add the custom function getPointerEvent, which tells us if the user uses a mouse or finger. On mouse down, we store the pointer X position, and on mouse move we call trackPointer().

  1. function getPointerEvent(event) {
  2.         return event.originalEvent.targetTouches ? event.originalEvent.targetTouches[0] : event;
  3. };
  4.  
  5. $("#threesixty").mousedown(function (event) {
  6.         event.preventDefault();
  7.         pointerStartPosX = getPointerEvent(event).pageX;
  8.         dragging = true;
  9. });
  10.  
  11. $(document).mouseup(function (event){
  12.         event.preventDefault();
  13.         dragging = false;
  14. });
  15.  
  16. $(document).mousemove(function (event){
  17.         event.preventDefault();
  18.         trackPointer(event);
  19. });

19. Touch events

Now add the touch events. We use the getPointerEvent() to pass the correct event to the trackPointer function. For all events, we prevent the defaultbehaviour and set the dragging value to true if the user is dragging the pointer, and to false for when they release.

  1. $("#threesixty").live("touchstart", function (event) {
  2.         event.preventDefault();
  3.         pointerStartPosX = getPointerEvent(event).pageX;
  4.         dragging = true;
  5. });
  6.  
  7. $("#threesixty").live("touchmove", function (event) {
  8.         event.preventDefault();
  9.         trackPointer(event);
  10. });
  11.  
  12. $("#threesixty").live("touchend", function (event) {
  13.         event.preventDefault();
  14.         dragging = false;
  15. });

20. Tracking movement

In trackPointer() we track the pointer movement periodically to make the frameanimation flow with the pointer dragging. As you can see, we only track the pointer if dragging is true and the application is ready. We do this because the rendering can be quite CPU intense and we have to be smart with the processing.

Store the start and end x pointer positions, then check the distance between them within the time periods. Using pointerDistance, calculate the animation endFrame and update it by calling the refresh function. speedMultiplier gives us full control over the spinning speed.

  1. function trackPointer(event) {
  2.         if (ready && dragging) {
  3.                 pointerEndPosX = getPointerEvent(event).pageX;
  4.                 if(monitorStartTime < new Date().getTime() - monitorInt) {
  5.                         pointerDistance = pointerEndPosX - pointerStartPosX;
  6.                         endFrame = currentFrame + Math.ceil((totalFrames - 1) * speedMultiplier * (pointerDistance / $("#threesixty").width()));
  7.                         refresh();
  8.                         monitorStartTime = new Date().getTime();
  9.                         pointerStartPosX = getPointerEvent(event).pageX;
  10.                 }
  11.         }
  12. };

21. We're done!

If you test the application you can see the percentage loader animation fading in and out, then the images showing up with the ‘swooshy’ spin. Now you can use the image slider with your mouse, or a touch on mobile devices, to make the 360 image slider spin with a responsive, smooth animation.

The best way to test the application on mobile devices is to place the project files onto a server, or share your localhost with the device. You can also see how different device screen resolutions and orientations are handled with media queries.

I have decided to make this slider open source, and created a git repository to allow others to collaborate and help to make it awesome.

26 comments

Comment: 1

HI,
thanks very much for this very handy article.
Just a question: which type of software do you use to create all those images?
Thanks a lot,
Claire

Comment: 2

Hi @claireguilloton,

I am not a 100% sure bit I think the guys used 3DMax or Maya and some After Effects for the images.

Cheers,
Rob

Comment: 4

Hi again,

apparently they used only 3DMax and After Effects only :)

Cheers,
Rob

Comment: 5

Wow this looks really nice. Although it takes a while to load the animation. Great job!

Comment: 6

This is amazing! Works on IE 6 too!
Is it possible to add or or 3 of these to one page?

Comment: 7

Hi @MysticDonut,

The original demo works with only one instance, but with some modification in the JavaScript code it could handle multiple instances of course. I'd suggest to stay tuned and watch the Github repository, I will definitely update the code, I am planning to add some cool features and make it a nice jQuery plugin in the not too distant future :)

Here is the GitHub repo:
https://github.com/heartcode/360-Image-Slider

Thanks,
Rob

Comment: 8

Awesome!
Thanks for the response Robert, I'll keep my eyes peeled :)

Regards,
Aid

Comment: 9

hello this is really cool. But i was found wow slider crack version. if you want wow slider crack version you can go crack wow slider

This is really cool.
Thanks

Comment: 10

Dear Robert

your 360 is amazin.

Comment: 11

Hi again rob

I tryed to open the code in notepad and it didn't open as it was in the wrong

format as the code in the mag is to small to read I tryed to Email you but I don't have your email address

my is archimedes965@yahoo.com

Comment: 12

Hi @Maverick,

If you are using Windows I'd recommend to download FlashDevelop. It's a free - and very good - code editor I used for years for ActionScript, PHP, XML and JavaScript (and lots more) coding. Give it a go!

On Mac you can get TextMate or Sublime Text 2, which are not free, but very awesome (and reasonably priced).

Regards,
Rob

Comment: 13

Hi,
Is there a way to change the code so that the images don't cycle? I need a start image on one side and the end image on the other, so you slide between. (Like I've done here with another code, but couldn't get it to work on touch devices: http://gifexhibit.tumblr.com)
Thanks!

Comment: 14

Hi,

This slider is so cool. Exactly what i needed.

Is there anyway how I could make the loading process faster?

Thanks!

Comment: 15

HI @johanna87, this feature will be implemented in the next release. I'd suggest to watch the github repo for updates.

@janaengel, You can use smaller, more optimized, or simply less images. I'd also recommend to use a CDN. Other than these there is not much you can do about loading times I'm afraid.

Cheers,
Rob

Comment: 16

how would you be able to add different links to the 3 6 0 ? is it even possible?

Comment: 17

Hi janaengel,

You could shave a lot of loding time by decreasing the number of frames. 180 frames (2 degree intervals) gives a wonderfully smooth experience , but if you would make 5 degree intervals you'll decrease the number of frames to 72 frames, which will speed up loading times.

What I would like to know is: can this script be adapted to make a full object-VR (so you could also view the top and/or bottom) It would be great if you could set a value to switch between sidescroll, sidescroll + top/bottom, full VR.

Great work,

Thanks,
Evert

Comment: 18

Really nice plugin. I just stumbled across this yesterday and haven't had much time to explore and experiment with it yet, but had a couple of questions that might help me speed up my own development.

- How would I go about integrating a callback? One that fires after the current animations have finished completely, AND a callback after each frame...

- I wanted to see if I could add "controls" (like left/right arrows) and/or detect keyboard input to animate forward or backward.

- Is it pretty straight forward to reverse the frame animation direction? For example, right now it counts up as you drag to the left, and down as you drag right. How would you reverse that?

I will keep working on adding these features, but any input to help me along would be SUPER appreciated! :)
Thanks

Comment: 19

So, I figured out how to reverse the frame animation as needed (The pointerDistance var in trackPointer function, set it to subtract START from END rather than END from START).

I also figured out how to insert the callbacks I needed, and went about putting together a function that would allow me to load up low-res versions of each frame on page load, and replace them with a high-res version when the animation stopped.

BUT, I ran into another issue that I can't seem to figure out. When I modify the src attribute on the current-image object, the plugin tries to add an additional frame to the sequence using the next image value (which obviously doesn't exist). Not sure why.. and it doesn't break the rotator, but it's causing errors that can't have.

Link to my dev copy:
http://www.crackin.com/dev/dodge/dart360/

Comment: 20

This is very nice and allows me to do a slightly modified version of the code. I also found that the loading was slow - this is because the images are loaded one after another, rather than allowing the images to download whenever possible. The bit that causes this is from part 11.

$(image).load(function() {
imageLoaded();
});

If this is changed to simply:

imageLoaded();

Then it loads much faster but the spinner does not work properly. There must be a combination of the two, which will help the page determine that the images have been loaded, whilst not doing it one at a time...

Comment: 21

Hi, Thanks for the great tutorial, i have a 3d sequence of 60 images. what should i change to adapt this slider? i changed the totalFrames to 60 and i don't realy understand how the "endFrame" value should be set on the "showThreesixty" function.

does anyone have a hint on that?

thanks in advence

Comment: 22

Love this! The comments within the code have really helped out to make updates.

Is there a way to change the code so that the images don't cycle all the way around? Just like johanna87, I need a start image on one side and the end image on the other, so you slide between. You had mentioned that this feature will be implemented in the next release, but I didn't see anything in the updates. Will this be added soon? Thank you!

Comment: 24

hi .....
it is wonderful.....i was trying to search article like this.....so thank you.....
but i want to know one thing.......
..................
which software or framework are you using to make this project in STEP 1....????????????
the one in which you composed jquery,CSS.....

Comment: 25

Hi!

Wonderful smooth plugin ! Is it possible to edit this script and use it to view the vertical line ? So you can see the bottom and the top ?

Thnx!

Comment: 26

I was wondering if it's possible to combine this with something like Shadowbox. So to have the 360 render pop out in a lightbox from a thumbnail.
August issue on sale now!

The Week in Web Design

Sign up to our 'Week in Web Design' newsletter!

Hosting Directory
.net digital edition
Treat yourself to our geeky merchandise!
site stat collection