Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSS only 3D spinning text

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>
like image 804
Richard Parnaby-King Avatar asked Jun 16 '15 16:06

Richard Parnaby-King


4 Answers

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>
like image 151
tonystar Avatar answered Oct 20 '22 05:10

tonystar


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>
like image 36
Okku Avatar answered Oct 20 '22 04:10

Okku


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>
like image 43
maioman Avatar answered Oct 20 '22 04:10

maioman


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)

like image 23
Richard Parnaby-King Avatar answered Oct 20 '22 06:10

Richard Parnaby-King