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>
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>
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>
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>
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With