CSS only 3D spinning text
Asked Answered
D

5

25

I have a div with some text spinning. How do I get the text depth to give a better 3d effect? To clarify, at 90deg the text becomes 1px thick because we can only see it from the side - how do I make it, eg, 10px thick? Also, the appropriate amount of depth should be shown - i.e. at 0deg we don't see the depth; at 45deg we see 5px of depth; at 90deg we see the full 10px depth; etc.

I am after a CSS only solution.

#spinner {
  animation-name: spinner;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-duration: 3s;
  transform-style: preserve-3d;
  text-align:center;
}
@keyframes spinner {
  from {
    transform: rotateY(0deg);
  }
  to {
    transform: rotateY(-360deg);
  }
}
<p id="spinner">Stop, I'm getting dizzy!</p>
Dah answered 16/6, 2015 at 16:6 Comment(2)
Gah! Which direction is it spinning? My brain can't make up its mind. XDWeaver
Hi Is this what you mean the-art-of-web.com/css/3d-transforms while the text is in 90deg the thickness should show like that?Landlord
R
34

Simple text-shadow can do the trick:

body {
  perspective: 500px;
}
#spinner {
  text-align: center;
  animation-name: spin, depth;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-duration: 3s;
}
@keyframes spin {
  from { transform: rotateY(0deg); }
  to { transform: rotateY(-360deg); }
}
@keyframes depth {
  0% { text-shadow: 0 0 black; }
  25% { text-shadow: 1px 0 black, 2px 0 black, 3px 0 black, 4px 0 black, 5px 0 black; }
  50% { text-shadow: 0 0 black; }
  75% { text-shadow: -1px 0 black, -2px 0 black, -3px 0 black, -4px 0 black, -5px 0 black; }
  100% { text-shadow: 0 0 black; }
}
<p id="spinner">Stop, I'm getting dizzy!</p>

Another improvement could be to clone the text using ::before and ::after pseudo-classes:

body {
  perspective: 1000px;
}
#spinner {
  font-size: 50px;
  text-align: center;
  animation-name: spin, depth;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-duration: 3s;
  transform-style: preserve-3d;
  position: relative;
}
#spinner::before,
#spinner::after {
  content: "Stop, I'm getting dizzy!";
  display: block;
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  transform: rotateY(0.5deg);
  transform-origin: 0 50%;
}
#spinner::after {
  transform: rotateY(-0.5deg);
  transform-origin: 100% 50%;
}
@keyframes spin {
  from { transform: rotateY(0deg); }
  to { transform: rotateY(-360deg); }
}
@keyframes depth {
  0% { text-shadow: 0 0 black; }
  25% { text-shadow: 1px 0 black, 2px 0 black, 3px 0 black, 4px 0 black, 5px 0 black, 6px 0 black; }
  50% { text-shadow: 0 0 black; }
  75% { text-shadow: -1px 0 black, -2px 0 black, -3px 0 black, -4px 0 black, -5px 0 black, -6px 0 black; }
  100% { text-shadow: 0 0 black; }
}
<p id="spinner">Stop, I'm getting dizzy!</p>
Rockaway answered 24/6, 2015 at 12:58 Comment(1)
from the (RUN CODE SNIPPET) we can see that the scroll bar below it gets activated. The spin should be in fixed div (max-width,min-width,width) to avoid it.Copyedit
C
5

A pure CSS solution will not look good. For a truly 3D effect, use JS. I recommend using three.js, because it has a fairly easy-to-use textGeometry function included.

I have written a script that replaces contents of elements with class rotatingText with a webGL scene where the text that was inside the element is rotating.

You can of course do all kinds of cool effects with three.js also to make your text look nicer. See textGeometry doc for text formatting. Look in the three.js docs for further details.

I put in some notes in the comments of the code.

.rotatingText {
  width: 100%;
  height: 200px;
}
<!--your rotating text, just give it class rotatingText-->
<div class="rotatingText">Stop, I'm getting dizzy!</div>


<!--three.js library-->
<script src="https://ajax.googleapis.com/ajax/libs/threejs/r69/three.min.js"></script>
<!--the default font for three.js-->
<script src="http://threejs.org/examples/fonts/helvetiker_regular.typeface.js"></script>
<!--the script that converts each .rotatingText element into 3D -->
<script>
  function rotateText(container,i){
    var t = THREE;
    var containerW = container.offsetWidth;
    var containerH = container.offsetHeight;
    var theText = container.innerHTML; //grab the text from the element...
    container.innerHTML = ""; // ...and clear it
    var renderer = new t.WebGLRenderer({
      alpha: true,
      antialiasing: true
    });
    renderer.setSize(containerW, containerH);
    renderer.setClearColor(0x000000, 0);
    container.appendChild(renderer.domElement);
    var scene = new t.Scene();
    var camera = new t.PerspectiveCamera(
      75, //vertical field of view
      containerW / containerH, //aspect ratio
      0.1, //near plane
      1000 //far plane
    );
    scene.add(camera);
    camera.position.set(0, 0, 100);
    // This is your 3D text:
    var geometry = new t.TextGeometry(theText, { //insert the text we grabbed earlier here
      size: 10, //your font size
      height: 1 //your font depth
    });
    var material = new t.MeshLambertMaterial({
      color: 0x000000 //your font color
    });
    var text = new t.Mesh(geometry, material); //this is your 3D text object
    //I created a pivot object to act as the center point for rotation:
    geometry.computeBoundingBox();
    var textWidth = geometry.boundingBox.max.x - geometry.boundingBox.min.x;
    text.position.set(-0.5 * textWidth, 0, 0);
    scene.add(text);
    var pivot = new t.Object3D();
    pivot.add(text);
    scene.add(pivot);
    //Then just render your scene and request for new frames
    this.render = function() {
      pivot.rotation.y += .02; //how much you want your text to rotate per frame
      requestAnimationFrame(render);
      renderer.render(scene, camera);
    }
    render();
  }

  var rotatingTextElements = document.getElementsByClassName("rotatingText");
  for (var i = 0; i < rotatingTextElements.length; i++) {
    rotateText(rotatingTextElements[i]);
  }
</script>
Collative answered 23/6, 2015 at 13:39 Comment(1)
I am aware of three.js, however I am after a CSS only solution.Dah
V
3

Adding multiple clones of the original element , each translated 1px behind, and then working on their color to give a 3d look isn't quite that bad,

check the Pen ( tested only on Chrome)


HTML

<section>
<p >Stop, I'm getting dizzy!</p>
</section>

SCSS

section{
  animation-name: spinner;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-duration: 4s;
  transform-style: preserve-3d;
}


p {
  text-align: center;
  font-size: 2em;
  position:absolute;
  width:100%;
  letter-spacing: 0.2em;
}

@keyframes spinner {
  from {
    transform: rotateY(0deg);
  }
  to {
    transform: rotateY(-360deg);
  }
}

$colors: #000,#111,#222,#333,#444,#555,#666,#777,#888,#999,#aaa,#bbb;

@for $i from 1 through length($colors) {
    p:nth-child( #{$i} ){
       transform: translateZ(- $i+px);
       color: (nth($colors, $i));

    }
}

JS

var p = document.querySelector('p');

for(var i = 0 ; i<13 ; i++){
var node = document.createElement('p');
var child = "Stop, I'm getting dizzy!";
  child = document.createTextNode(child);
  node.appendChild(child);
p.parentNode.appendChild(node);
}

section {
  animation-name: spinner;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-duration: 4s;
  transform-style: preserve-3d;
}

p {
  text-align: center;
  font-size: 2em;
  position: absolute;
  width: 100%;
  letter-spacing: 0.2em;
}

p:last-child {
  -webkit-text-fill-color: silver;
}

@keyframes spinner {
  from {
    transform: rotateY(0deg);
  }
  to {
    transform: rotateY(-360deg);
  }
}

p:nth-child(1) {
  transform: translateZ(-1px);
  color: #000;
}

p:nth-child(2) {
  transform: translateZ(-2px);
  color: #111;
}

p:nth-child(3) {
  transform: translateZ(-3px);
  color: #222;
}

p:nth-child(4) {
  transform: translateZ(-4px);
  color: #333;
}

p:nth-child(5) {
  transform: translateZ(-5px);
  color: #444;
}

p:nth-child(6) {
  transform: translateZ(-6px);
  color: #555;
}

p:nth-child(7) {
  transform: translateZ(-7px);
  color: #666;
}

p:nth-child(8) {
  transform: translateZ(-8px);
  color: #777;
}

p:nth-child(9) {
  transform: translateZ(-9px);
  color: #888;
}

p:nth-child(10) {
  transform: translateZ(-10px);
  color: #999;
}

p:nth-child(11) {
  transform: translateZ(-11px);
  color: #aaa;
}

p:nth-child(12) {
  transform: translateZ(-12px);
  color: #bbb;
}
 <section>
   <p>Stop, I'm getting dizzy!</p>
   <p>Stop, I'm getting dizzy!</p>
   <p>Stop, I'm getting dizzy!</p>
   <p>Stop, I'm getting dizzy!</p>
   <p>Stop, I'm getting dizzy!</p>
   <p>Stop, I'm getting dizzy!</p>
   <p>Stop, I'm getting dizzy!</p>
   <p>Stop, I'm getting dizzy!</p>
   <p>Stop, I'm getting dizzy!</p>
   <p>Stop, I'm getting dizzy!</p>
   <p>Stop, I'm getting dizzy!</p>
   <p>Stop, I'm getting dizzy!</p>
   </section>
Vaudeville answered 23/6, 2015 at 14:49 Comment(3)
Whilst this effect does look better than a pure css solution, I am limited to pure css.Dah
@RichardParnaby-King the JS and SCSS part is only programmatic; the final output is just HTML and CSSVaudeville
You're using JS to create 13 p tags. I cannot add more HTML elements and I am limited to CSS (no JS).Dah
D
2

I have added extra layers of the text using :before and :after elements, and I have offset their Y axis by -3deg and 3deg respectively. The result is that the text now has a "sort of" depth. If I use a shorter string (e.g. "RPK") I can increase the offset to about 7deg and still have an OK look. However, this only gives the outer edges an extra "depth".

#spinner {
  animation-name: spinner;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-duration: 3s;
  transform-style: preserve-3d;
  text-align:center;
  font-size:2em;
}
#spinner:after,
#spinner:before {
  content:"Stop, I'm getting dizzy!";
  position:absolute;
  top:0;
  left:0;
  width:100%;
  text-align:center;
  transform: rotateY(2deg);
}
#spinner:before {
  transform: rotateY(-2deg);
}
#spinner,
#spinner:after,
#spinner:before {
  font-weight:narrow;
}
@keyframes spinner {
  from {
    transform: rotateY(0deg);
  }
  to {
    transform: rotateY(-360deg);
  }
}
<p id="spinner">Stop, I'm getting dizzy!</p>

In this version I have changed the rotate property for translateZ. Again, this effect looks better with a shorter string.

#spinner {
  animation-name: spinner;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-duration: 3s;
  transform-style: preserve-3d;
  text-align: center;
  font-size: 2em;
}
#spinner:after,
#spinner:before {
  content: "Stop, I'm getting dizzy!";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  text-align: center;
  transform: translateZ(3px);
}
#spinner:before {
  transform:translateZ(-3px);
}
@keyframes spinner {
  from {
    transform: rotateY(0deg);
  }
  to {
    transform: rotateY(-360deg);
  }
}
<p id="spinner">Stop, I'm getting dizzy!</p>

I'm not going to accept this as the answer because it does not give me the exact effect I am after. I am sure someone out there will find the perfect method :o)

Dah answered 17/6, 2015 at 16:28 Comment(0)
R
1

I also wanted CSS-driven rotating text, though my approach was different. I'll add it in case it helps others:

@keyframes spin {
  from {
    transform: rotateY(0deg) rotateX(0deg);
  }
  to {
    transform: rotateY(360deg) rotateX(360deg);
  }
}

#shared-loader {
  box-sizing: border-box;
  font-family: sans-serif;
  width: 200px;
  height: 200px;
  margin: 0;
  perspective: 400px;
  transform: scale(0.2);
}
    
.cube {
  width: 200px;
  height: 200px;
  position: relative;
  transform-style: preserve-3d;
  transform: translateZ(-100px);
  transition: transform 2s;
  animation: spin 6s linear infinite;
}

.cube-face {
  position: absolute;
  width: 200px;
  height: 200px;
  border: 2px solid #fff;
  line-height: 200px;
  font-size: 80px;
  font-weight: bold;
  color: white;
  text-align: center;
  background: #de662c;
}

.cube-face-front  { transform: rotateY(  0deg) translateZ(100px); }
.cube-face-right  { transform: rotateY( 90deg) translateZ(100px); }
.cube-face-back   { transform: rotateY(180deg) translateZ(100px); }
.cube-face-left   { transform: rotateY(-90deg) translateZ(100px); }
.cube-face-top    { transform: rotateX( 90deg) translateZ(100px); }
.cube-face-bottom { transform: rotateX(-90deg) translateZ(100px); }
<div id="shared-loader">
  <div class="cube">
    <div class="cube-face cube-face-front">Y</div>
    <div class="cube-face cube-face-back">Y</div>
    <div class="cube-face cube-face-right">Y</div>
    <div class="cube-face cube-face-left">Y</div>
    <div class="cube-face cube-face-top">Y</div>
    <div class="cube-face cube-face-bottom">Y</div>
  </div>
</div>
Roulette answered 25/1, 2022 at 13:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.