本文将介绍如何一步一步制作出一些3D效果的云彩,会用到一些高级的属性,主要是如何通过CSS的3D变换效果来实现,如果你想了解更多,这地方是个不错的开始。
静态效果图
本教程将会分成几个部分,每一部分都会有详细的步骤让你理解HTML,CSS和Javascript都是如何工作的,每一步都有衔接,以及一个链接来测试单个部分的代码效果。教程里的代码是最终demo的简化版,但是主要区别在每一部分都有说明。
首先,我们需要两个div元素: viewport 和 world。剩下的部分将会在后面动态的加入。
viewport 包含了整一个屏幕和一个摄影机。由于在CSS 3D变换中没有摄影机本身,就假想你在通过一个透明的玻璃屏幕来看屏幕里的视野,你也可以改变看视野的方向。我们将会把所有对象都放在里面,然后对他们进行变换。
world 是一个用来固定所有对象的 div 盒子。 旋转,转化或者放大都变换都会改变我们的元素。为简单起见,我将不使用CSS属性前缀,而是使用浏览器前缀(-webkit,-moz,-0,-ms等等)。
这就是我们需要的所有盒子模型:
<div id="viewport"> <div id = "world" ></div></div>
下面是我们定义的两个CSS样式,这里非常重要的是要将 world div 放在 viewport div里面,否则将无法渲染场景。要记住你将旋转一个放置在文档里的元素,像其他2D元素那样。
#viewport{ bottom:0; left:0; overflow:hidden; perspective:400; position:absolute; right:0; top:0;}#world{ height: 512px; left: 50%; margin-left: -256px; margin-top: -256px; position: absolute; top: 50%; transform-style: preserve-3d; width: 512px;}
现在写一些代码来初始化我们的对象,绑定 mousemove 事件以及定义 updateView() 函数。
/* Defining our variables world and viewport are DOM elements, worldXAngle and worldYAngle are floats that hold the world rotations, d is an int that defines the distance of the world from the camera */var world = document.getElementById( 'world' ), viewport = document.getElementById( 'viewport' ), worldXAngle = 0, worldYAngle = 0, d = 0;/* Event listener to transform mouse position into angles from -180 to 180 degress, both vertically and horizontally*/window.addEventListener( 'mousemove', function( e ) { worldYAngle = -( .5 - ( e.clientX / window.innerWidth ) ) * 180; worldXAngle = ( .5 - ( e.clientY / window.innerHeight ) ) * 180; updateView();} );/* Changes the transform property of world to be translated in the Z axis by d pixels, rotated in the X axis by worldXAngle degrees and rotated in the Y axis by worldYAngle degrees.*/function updateView() { world.style.transform = 'translateZ( ' + d + 'px ) \ rotateX( ' + worldXAngle + 'deg) \ rotateY( ' + worldYAngle + 'deg)';}
world 是红色的,viewport 有背景色来模拟天空,mousewheel滚轮事件来监听摄影机的远近距离。移动鼠标来观察红色的 div 是如何改变方向的。
这是这一部分的演示链接
现在我们开始添加真正的3D内容。我们加入一些新的 div 放置在 world 空间里。有必要添加几个绝对位置的 div 作为 world 的子节点,但是用3D变换来替代 left 和 top, 他们默认会出现在 world 的中央位置。width 和 height属性无关紧要,因为这些新的容器是放置那些真实云的层。如果正式应用里最好将他们居中(通过设置 margin-left 和 margin-top 属性 来设置 width 和 height 一半的负数值)。
.cloudBase { height: 20px; left: 256px; margin-left: -10px; margin-top: -10px; position: absolute; top: 256px; width: 20px;}
我们添加 generate() 和 createCloud() 方法来充实 world。注意 random_{var} 不是真实的变量值,而是一个代码占位符,应该返回一个给定范围内的随机数。
/* objects is an array of cloud bases layers is an array of cloud layers*/ var objects = [], layers = [];/* Clears the DOM of previous clouds bases and generates a new set of cloud bases*/ function generate() { objects = []; layers = []; if ( world.hasChildNodes() ) { while ( world.childNodes.length >= 1 ) { world.removeChild( world.firstChild ); } } for( var j = 0; j <; 5; j++ ) { objects.push( createCloud() ); } }/* Creates a single cloud base: a div in world that is translated randomly into world space. Each axis goes from -256 to 256 pixels.*/ function createCloud() { var div = document.createElement( 'div' ); div.className = 'cloudBase'; var t = 'translateX( ' + random_x + 'px ) \ translateY( ' + random_y + 'px ) \ translateZ( ' + random_z + 'px )'; div.style.transform = t; world.appendChild( div ); return div; }
页面中那些粉红色方块的层就是云层模型了,这里有一个 p 变量来更简单的改变 viewport.style.perspective 的值。试着去改变这个变量来观察我们的摄影机是如何变化的。这个值越大,视角的垂直度就会越大。再次提醒,那个 randowm_{var} 并不是真正的变量。
点我来观察这部分的演示
现在有趣的事情开始发生了,我们添加了几个绝对位置的“云层” div 盒子来表示云,这些盒子将会用来装载云的贴图。
.cloudLayer { height: 256px; left: 50%; margin-left: -128px; margin-top: -128px; position: absolute; top: 50%; width: 256px;}
旧的 createCloud() 函数做了一些改动,添加了云层的随机数。
/* Creates a single cloud base and adds several cloud layers. Each cloud layer has random position ( x, y, z ), rotation (a) and rotation speed (s). layers[] keeps track of those divs.*/function createCloud() { var div = document.createElement( 'div' ); div.className = 'cloudBase'; var t = 'translateX( ' + random_x + 'px ) \ translateY( ' + random_y + 'px ) \ translateZ( ' + random_z + 'px )'; div.style.transform = t; world.appendChild( div ); for( var j = 0; j < 5 + Math.round( Math.random() * 10 ); j++ ) { var cloud = document.createElement( 'div' ); cloud.className = 'cloudLayer'; cloud.data = { x: random_x, y: random_y, z: random_z, a: random_a, s: random_s }; var t = 'translateX( ' + random_x + 'px ) \ translateY( ' + random_y + 'px ) \ translateZ( ' + random_z + 'px ) \ rotateZ( ' + random_a + 'deg ) \ scale( ' + random_s + ' )'; cloud.style.transform = t; div.appendChild( cloud ); layers.push( cloud ); } return div;}
点我来观察第三部分的演示
云层就是那些蓝色带有一点白色边边的部分,看起来相当有层次感呐。移动鼠标来观察下每一层的位置是怎样的以及如何旋转的。
接下来就是见证奇迹的时刻!我们用 layers[] 来为世界里每一个单独的云层建立一个引用。我们用 worldXangle 和 worldYAngle 来表示整个空间的选择变换。
如果我们将每个层都设置成相反的旋转,这会在viewport里重新调整他们:我们就有一个布告板了。因为我们旋转world先是X方向然后是Y方向,我们需要反着顺序来旋转每一个层,首先是Y方向再是X方向,变换的顺序是非常重要的,如果你没有正确的设置顺序,元素的方向就都会不对了。
/* Iterate layers[], update the rotation and apply the inverse transformation currently applied to the world. Notice the order in which rotations are applied.*/function update (){ for( var j = 0; j < layers.length; j++ ) { var layer = layers[ j ]; layer.data.a += layer.data.speed; var t = 'translateX( ' + layer.data.x + 'px ) \ translateY( ' + layer.data.y + 'px ) \ translateZ( ' + layer.data.z + 'px ) \ rotateY( ' + ( - worldYAngle ) + 'deg ) \ rotateX( ' + ( - worldXAngle ) + 'deg ) \ scale( ' + layer.data.s + ')'; layer.style.transform = t; } requestAnimationFrame( update );}
来回的移动鼠标,你将会看到蓝色的云层现在已经变成垂直的了(他们总是面对这镜头),但是 world 和其他云基体还是三维空间里的对象。
点我来见证神奇的效果
为了达到最后的效果,只需要将用来调试的那些颜色去掉,把云层 div 用一个 img 贴上云的图片。注意图片得是带有alpha通道的PNG格式,要不没效果。
点我看看最后的效果
点我看看最后版本
当然,你可以随意换其他你想要的图片:烟雾痕迹,等离子体云,绿叶,飞行的面包机等等。只要把 backgroud-image 更改下就好了。混合不同比例的纹理材质会有神奇的效果。
随机的添加元素是可以的,但是你也可以创建有序的结构,比如树,鸭子形状的云或者复杂的大爆炸等。可以尝试一些3D曲线,创建固定的云的运行轨迹,创造一个多人游戏来猜3D云的形状等等,充满着无限可能。
NOTE:文中的代码都是简化了的哦,如果想一步一步实际操作的话最好保存下示例效果的链接页面来查看源码。
本文译自 https://www.clicktorelease.com/blog/how-to-make-clouds-with-css-3d#涉及到一些3D透视等专业词汇翻译有误敬请见谅,欢迎指正。