Hi DW, I'm having an issue with my code, I'm trying to animate the rolling of the ball, I want it to have faces where it will roll to the back and simulate 3D by also revealing the backside now it just display separated but I want it to look like a solid ball rolling.
How can I fix this issue?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Striped 10-Ball</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            background-color: #0b6623; /* Billiard table green */
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            overflow: hidden;
            perspective: 1000px;
        }

        /* 3D Scene Wrapper */
        .scene {
            width: 200px;
            height: 200px;
            position: relative;
        }

        /* The Ball Structure */
        .ball {
            width: 100%;
            height: 100%;
            position: absolute;
            transform-style: preserve-3d;
            cursor: grab;
        }

        .ball:active {
            cursor: grabbing;
        }

        /* Base Sphere Shape using CSS Overlays */
        .layer {
            position: absolute;
            width: 100%;
            height: 100%;
            border-radius: 50%;
            backface-visibility: visible;
        }

        /* 
           The 10-ball has a blue stripe. 
           We create this using a repeating linear gradient.
        */
        .stripe-layer {
            background: linear-gradient(
                to bottom,
                #ffffff 0%, #ffffff 25%,
                #0055a5 25%, #0055a5 75%,
                #ffffff 75%, #ffffff 100%
            );
        }

        /* Front and Back Number Badges (Faces) */
        .badge {
            position: absolute;
            width: 60px;
            height: 60px;
            background: #ffffff;
            border-radius: 50%;
            top: 70px;
            left: 70px;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: 'Arial Black', sans-serif;
            font-size: 32px;
            font-weight: bold;
            color: #000000;
            box-shadow: inset 0 0 10px rgba(0,0,0,0.2);
            backface-visibility: hidden; /* Hides when turned around */
        }

        /* Position the Front Face */
        .face-front {
            transform: translateZ(99px);
        }

        /* Position the Back Face (rotated 180 degrees) */
        .face-back {
            transform: rotateY(180deg) translateZ(99px);
        }

        /* Subtle lighting overlay for realistic 3D appearance */
        .shading {
            background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.4) 0%, rgba(0,0,0,0.5) 80%);
            transform: translateZ(101px);
            pointer-events: none;
        }
    </style>
</head>
<body>

    <div class="scene">
        <div class="ball" id="poolBall">
            <!-- Striped Base -->
            <div class="layer stripe-layer"></div>

            <!-- Front Face -->
            <div class="badge face-front">10</div>

            <!-- Back Face -->
            <div class="badge face-back">10</div>

            <!-- Lighting overlay -->
            <div class="layer shading"></div>
        </div>
    </div>

    <script>
        const ball = document.getElementById('poolBall');

        // Initial rotation coordinates
        let targetX = 0;
        let targetY = 0;
        let currentX = 0;
        let currentY = 0;

        let isDragging = false;
        let previousMousePosition = { x: 0, y: 0 };

        // Auto-roll physics when not dragging
        let velocityX = 0.5;
        let velocityY = 0.8;

        // Interaction Listeners
        window.addEventListener('mousedown', (e) => {
            isDragging = true;
            previousMousePosition = { x: e.clientX, y: e.clientY };
        });

        window.addEventListener('mousemove', (e) => {
            if (!isDragging) return;

            const deltaX = e.clientX - previousMousePosition.x;
            const deltaY = e.clientY - previousMousePosition.y;

            // Map mouse movement to 3D matrix rotation speeds
            targetY += deltaX * 0.5;
            targetX -= deltaY * 0.5;

            // Track instant velocity for inertia
            velocityX = -deltaY * 0.1;
            velocityY = deltaX * 0.1;

            previousMousePosition = { x: e.clientX, y: e.clientY };
        });

        window.addEventListener('mouseup', () => {
            isDragging = false;
        });

        // Touch Support for Mobile
        window.addEventListener('touchstart', (e) => {
            isDragging = true;
            previousMousePosition = { x: e.touches[0].clientX, y: e.touches[0].clientY };
        });

        window.addEventListener('touchmove', (e) => {
            if (!isDragging) return;
            const deltaX = e.touches[0].clientX - previousMousePosition.x;
            const deltaY = e.touches[0].clientY - previousMousePosition.y;

            targetY += deltaX * 0.5;
            targetX -= deltaY * 0.5;

            velocityX = -deltaY * 0.1;
            velocityY = deltaX * 0.1;

            previousMousePosition = { x: e.touches[0].clientX, y: e.touches[0].clientY };
        });

        window.addEventListener('touchend', () => {
            isDragging = false;
        });

        // Smooth Animation Loop
        function animate() {
            if (!isDragging) {
                // Apply constant rolling and friction deceleration over time
                targetX += velocityX;
                targetY += velocityY;

                // Keep the ball slowly rolling infinitely
                velocityX *= 0.98;
                velocityY *= 0.98;

                // Minimum ambient rolling speed
                if (Math.abs(velocityX) < 0.1) velocityX = 0.2;
                if (Math.abs(velocityY) < 0.1) velocityY = 0.3;
            }

            // Interpolate values for buttery smooth movement (lerp)
            currentX += (targetX - currentX) * 0.1;
            currentY += (targetY - currentY) * 0.1;

            // Apply 3D matrix transformation to rotate the ball in all directions
            ball.style.transform = `rotateX(${currentX}deg) rotateY(${currentY}deg)`;

            requestAnimationFrame(animate);
        }

        // Initialize Animation Loop
        animate();
    </script>
</body>
</html>

Recommended Answers

All 6 Replies

I've come to think of another option, Instead of actualing rolling or flipping the entire ball, I think it will be best to only flip the stripe as well as the number and just move the entire ball while animating the stripe using

.stripe-layer {
            background: linear-gradient(
                to bottom,
                #ffffff 0%, #ffffff 25%,
                #0055a5 25%, #0055a5 75%,
                #ffffff 75%, #ffffff 100%
            );

to play around with it by changing the percentage values at certain degrees of the ball movement in the @keyframe{} but I don't know how I can do that even if it would be in JavaScript . Remember the spin is perfect the problem is rolling from your front view point to the back side and vise verse.

Any idea on how to achieve this?

I was looking at https://threejs.org/ that looks pretty powerful.

I think making a 3D object(as in it's full sense - a complete 3d model) is a bit out of scope for raw Javascript & CSS in a HTML page - sure you can make 3D images and geometric objects but when you start moving into a detailed 3D object the amount of raw pictures you would need to make it look real is insane and likely resource heavy. There is a HTML5 canvas that you could probably work out painting 3D objects in frames most likely - that is basically javascript.

I know pixel games used a trick of having 8 different angled images/gifs - so it would pick 1 of 8 gifs based on the direction you are moving - but that was only 2D. You might be able to add a perspective on one plane to give a little feeling of it being 3D.

Personally I would go down the route of some 3D rendering plugin or the HTML5 Canvas if I really wanted native HTML support.

rather than making an existing object move on the page, you have script that pre-renders what something will look like and get it to paint it on screen at 30 or 60 fps, I would assume that would be most resource efficient but I haven't gone much into that area beyond making 2d simple sprite games.

happy i looked into this - I may end up using three.js myself for some project

commented: threejs has DOOM - awesome... +17

Thanks, I will have a look at it and see how I can utilize it

I have done similar in Javascript/canvas, which was plenty performant enough for my application (a magnifying lens effect, published as an answer somewhere on Stackexchange).

Your initial decision will be how to describe your sphere's surface image.

The main options are:-

  1. Equirectangular Map (2D Raster Grid).
  2. Cube Map (Six 2D Grids).
  3. HEALPix (Hierarchical Equal Area isoLatitude Pixelization).
  4. Spherical Quadtree.

Once that's decided and the chosen data structure is populated with data (one r|g|b|alpha set per pixel), then-:

  1. Model the ball orientation wrt time, in particular calculate the face presented to the observer at each iteration.

  2. Develop a paradigm to-:
    a) iterate through every pixel of the 2d area on your canvas where you want the ball to appear.
    b) calculate for each pixel the r|g|b|alpha location within the data structure for the desired pixel.
    c) write the r|g|b|alpha values into the canvas's "2d" structure (which is confusingly addressed as a stonking great 1d array).

That's the essence of it anyway.

2(b) will be tricky whichever data structure you choose as you will effectively have to "stitch" image parts together. That's an issue I didn't have with my lens.

commented: Thanks, this sounds like too much work and a bit of math +9

thanks for the contribution, I've came to a decision to not actually turn/spin/flip the actual ball but rather to move it as the base color is white but only play around with the stripes color to animate the rolling i'm looking for. Please take a look at the below code and see what i've done so far. I'm thinking of using the Three.js for camera or the shinning of the ball.

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style type="text/css">
    :root {
  --ball-size: 150px;
  --maroon: #800020;
  --white: #f5f5f5;
}

body {
  margin: 0;
  background-color: #222;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  overflow: hidden;
}

/* Pool Table Surface */
.table {
  width: 600px;
  height: 300px;
  background: radial-gradient(circle at center, #2e8b57, #1e5631);
  border: 12px solid #4a2c11;
  border-radius: 8px;
  box-shadow: inset 0 0 20px rgba(0,0,0,0.6), 0 20px 30px rgba(0,0,0,0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

/* Physics Container for Rolling Translation */
.ball-container {
  position: relative;
  width: var(--ball-size);
  height: var(--ball-size);
  animation: roll-across 5s ease-in-out infinite alternate;
}

/* The Sphere with Seamless Striping & 3D Shading */
.ball {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  position: relative;

  /* Base texture: Creates a crisp maroon stripe with white shoulders */
  background: linear-gradient(
    to bottom,
    var(--white) 0%,
    var(--white) 25%,
    var(--maroon) 25%,
    var(--maroon) 75%,
    var(--white) 75%,
    var(--white) 100%

  );

  /* Pseudo-element combines the 3D sphere reflections so it looks volumetric */
  box-shadow: 
    inset -15px -15px 40px rgba(0, 0, 0, 0.8), 
    inset 15px 15px 35px rgba(255, 255, 255, 0.4);

  display: flex;
  align-items: center;
  justify-content: center;

  /* Link rotation speed to the translation speed */
  animation: spin 5s ease-in-out infinite alternate;
}

/* The Number 15 Circle Badge */
.number-badge {
  width: calc(var(--ball-size) * 0.45);
  height: calc(var(--ball-size) * 0.45);
  background-color: var(--white);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Arial Black', sans-serif;
  font-size: calc(var(--ball-size) * 0.22);
  font-weight: bold;
  color: #111;
  box-shadow: inset 2px 2px 5px rgba(0,0,0,0.3);

  /* Subtle skew to mimic sphere surface curvature */
  transform: rotate(-10deg); 
  user-select: none;
}

/* Ambient Floor Shadow */
.shadow {
  position: absolute;
  bottom: -10px;
  left: 5%;
  width: 90%;
  height: 15px;
  background: rgba(0, 0, 0, 0.4);
  border-radius: 50%;
  filter: blur(4px);
  z-index: -1;
}

/* --- Animations --- */

/* Moves the ball back and forth across the table */
@keyframes roll-across {
  0% {
    transform: translateY(-180px);
  }
  100% {
    transform: translateY(180px);
  }
}

/* Spins the ball texture and badge simultaneously */
@keyframes spin {
  0% {
    /*transform: rotateZ(-200deg);*/
    background: linear-gradient(
    to bottom,
    var(--white) 0%,
    var(--white) 25%,
    var(--maroon) 25%,
    var(--maroon) 75%,
    var(--white) 75%,
    var(--white) 100%

  );
  }
  25% {
    background: linear-gradient(
    to bottom,
    var(--white) 50%,
    var(--white) 50%,
    var(--white) 10%,
    var(--maroon) 50%,
    var(--maroon) 80%,
    var(--maroon) 20%

  );
  }
  50% {
        background: linear-gradient(
    to bottom,
    var(--maroon) 20%,
    var(--white) 25%,
    var(--white) 25%,
    var(--white) 75%,
    var(--maroon) 75%,
    var(--maroon) 100%

  );
  }
  75% {
    background: linear-gradient(
    to bottom,
    var(--maroon) 50%,
    var(--maroon) 50%,
    var(--white) 25%,
    var(--white) 75%,
    var(--white) 75%,
    var(--white) 50%

  );
  }
  100% {
    /*transform: rotateZ(200deg);*/
    filter: contrast(1);
  }
}


    </style>
</head>
<body>
<div class="table">
  <div class="ball-container">
    <div class="ball">
      <!-- The number badge 
      <div class="number-badge">15</div>-->
    </div>
    <!-- Realistic shadow that scales with movement -->
    <div class="shadow"></div>
  </div>
</div>

</body>
</html>
commented: You are as lazy and dismissive in equal measures. Rest assured, I will not be answering any of your questions again. -3

No, I'm not being lazy and dismissive don't get me wrong, just that this has been giving me a problem for quie sometime and I've tried many suggestion and others I'm still about to try. Another this is that I'm not sure if I'm clearly stating what I'm trying to achieve, sorry I'm not fluent in english.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.