教程中的代码是演示的简化版本,但是主要区别记录在每个部分中。

1. 创造world和camera

首先,我们需要两个div元素:viewportworld。其余所有元素将动态创建。
viewport覆盖整个屏幕,并充当相机平面。由于在CSS 3D Transforms中本身没有camera,因此可以将其视为静止的玻璃板,通过它可以看到一个相对于您改变方向的世界。我们将所有camera对象(或scene)放置在其中,这就是将要转换的内容。
World是一个div,我们将使用它来锚定所有3D元素。变换(旋转,平移或缩放)world将改变我们所有的元素

Viewport and world container
HTML - index.html

<div id="viewport" >
  <div id="world" ></div>
</div>

接下来是我们的两个CSS定义。将包含场景的div(在我们的情况下为world)居中放置在Viewport 中非常重要,否则场景将偏移显示!

Viewport and world container
CSS-index.html

#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()

mousemove and updateView
JavaScript-index.html
/*
  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 具有CSS背景以模拟天空,并且有鼠标滚轮事件监听器可修改相机的距离。移动鼠标,注意红色div如何改变方向。

2.向我们的世界添加对象

现在我们开始添加真实的3D内容。我们添加一些相对于world定位在空间中的新div。从本质上讲,它是将几个绝对位置的div添加为world的子级。默认情况下,它们位于world中央。对于云,最好将它们居中(通过将左边距左边和顶部边距设置为宽度和高度的负一半)。

Cloud base styling
CSS - index.html
.cloudBase {
  height: 20px;
  left: 256px;
  margin-left: -10px;
  margin-top: -10px;
  position: absolute;
  top: 256px;
  width: 20px;
}

我们添加了generate()createCloud()函数来填充world。请注意,random_ {var}不是实数变量,而是实数代码的占位符名称,应返回指定范围之间的随机数。

Create and distribute clouds
JavaScript - index.html
/*
  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。再次注意,random_ {var}不是变量。
粉红色的正方形

粉红色的正方形

3.在我们的对象上添加图层

现在事情开始变得有趣起来。我们向每个.cloudBase添加几个绝对定位的.cloudLayer div元素。这些将保留我们的云纹理。

Cloud layers styling
CSS - index.html

.cloudLayer {
  height: 256px;
  left: 50%;
  margin-left: -128px;
  margin-top: -128px;
  position: absolute;
  top: 50%;
  width: 256px;
}

对旧的createCloud()进行了一些更改,以添加随机数量的cloudLayers。

Improved createCloud() to add layers
JavaScript - index.html
/*
  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;
}

变成了这个鸟样子

变成了这个鸟样子

4.使3D效果生效

这就是魔术发生的地方。我们的layers []包含了我们world中每个单层的参考,并且我们有worldXangleworldYAngle,它们是应用于我们世界的变换的旋转部分。如果我们对每个图层应用相反的旋转,我们将有效地将它们重新对齐到viewpoint。转换顺序非常重要。如果您未按正确的顺序应用它们,则元素的方向将错误!

Aligning cloud layers with camera
JavaScript - index.html
/*
  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 );

}

又成了这个鸟样

又成了这个鸟样

5.最后的话

为了获得最终效果,只需删除调试颜色,然后将.cloudLayer div更改为具有云纹理的img。纹理应为具有Alpha通道的PNG,以正确获得效果。
看吧,云出来了

看吧,云出来了

当然,您可以使用任何所需的纹理或一组纹理:烟熏,等离子云,绿叶,飞行的烤面包机...只需更改特定种类的云层使用的背景图像。以不同的比例混合不同的纹理可获得有趣的结果。

效果图

效果图

附上最后的代码:

css



<html><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1252">

    <style>
    *{
        box-sizing: border-box;
        margin: 0;
        padding: 0
    }
    body {
        color: #eee;
        text-shadow: 0 -1px 0 rgba( 0, 0, 0, .6 );
        font-family: 'Open Sans', sans-serif;
        font-size: 13px;
        line-height: 16px;
        overflow: hidden;
    }
    #viewport {
        -webkit-perspective: 1000;
        -moz-perspective: 1000;
        -o-perspective: 1000;
        position: absolute;
        left: 0;
        top: 0;
        right: 0;
        bottom: 0;
        overflow: hidden;
        background-image: linear-gradient(bottom, rgb(69,132,180) 28%, rgb(31,71,120) 64%);
        background-image: -o-linear-gradient(bottom, rgb(69,132,180) 28%, rgb(31,71,120) 64%);
        background-image: -moz-linear-gradient(bottom, rgb(69,132,180) 28%, rgb(31,71,120) 64%);
        background-image: -webkit-linear-gradient(bottom, rgb(69,132,180) 28%, rgb(31,71,120) 64%);
        background-image: -ms-linear-gradient(bottom, rgb(69,132,180) 28%, rgb(31,71,120) 64%);

        background-image: -webkit-gradient(
            linear,
            left bottom,
            left top,
            color-stop(0.28, rgb(69,132,180)),
            color-stop(0.64, rgb(31,71,120))
        );
    }

    #world {
        position: absolute;
        left: 50%;
        top: 50%;
        margin-left: -256px;
        margin-top: -256px;
        height: 512px;
        width: 512px;
        -webkit-transform-style: preserve-3d;
        -moz-transform-style: preserve-3d;
        -o-transform-style: preserve-3d;
    }

    #world div {
        -webkit-transform-style: preserve-3d;
        -moz-transform-style: preserve-3d;
        -o-transform-style: preserve-3d;
    }

    .cloudBase {
        position: absolute;
        left: 256px;
        top: 256px;
        width: 20px;
        height: 20px;
        margin-left: -10px;
        margin-top: -10px;
    }

    .cloudLayer {
        position: absolute;
        left: 50%;
        top: 50%;
        width: 256px;
        height: 256px;
        margin-left: -128px;
        margin-top: -128px;
        -webkit-transition: opacity .5s ease-out;
        -moz-transition: opacity .5s ease-out;
        -o-transition: opacity .5s ease-out;
    }
    </style>
    </head>
    <body>

    <div id="viewport" style="perspective: 400px;">
        <div id="world"><div class="cloudBase"></div>
    </div>

    <script>

    (function() {
        var lastTime = 0;
        var vendors = ['ms', 'moz', 'webkit', 'o'];
        for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
            window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
            window.cancelRequestAnimationFrame = window[vendors[x]+
              'CancelRequestAnimationFrame'];
        }

        if (!window.requestAnimationFrame)
            window.requestAnimationFrame = function(callback, element) {
                var currTime = new Date().getTime();
                var timeToCall = Math.max(0, 16 - (currTime - lastTime));
                var id = window.setTimeout(function() { callback(currTime + timeToCall); },
                  timeToCall);
                lastTime = currTime + timeToCall;
                return id;
            };

        if (!window.cancelAnimationFrame)
            window.cancelAnimationFrame = function(id) {
                clearTimeout(id);
            };
    }())

    var layers = [],
        objects = [],

        world = document.getElementById( 'world' ),
        viewport = document.getElementById( 'viewport' ),

        d = 0,
        p = 400,
        worldXAngle = 0,
        worldYAngle = 0;

    viewport.style.webkitPerspective = p;
    viewport.style.MozPerspective = p;
    viewport.style.oPerspective = p;

    generate();

    function createCloud() {

        var div = document.createElement( 'div'  );
        div.className = 'cloudBase';
        var x = 256 - ( Math.random() * 512 );
        var y = 256 - ( Math.random() * 512 );
        var z = 256 - ( Math.random() * 512 );
        var t = 'translateX( ' + x + 'px ) translateY( ' + y + 'px ) translateZ( ' + z + 'px )';
        div.style.webkitTransform = t;
        div.style.MozTransform = t;
        div.style.oTransform = t;
        world.appendChild( div );

        for( var j = 0; j < 5 + Math.round( Math.random() * 10 ); j++ ) {
            var cloud = document.createElement( 'img' );
            cloud.style.opacity = 0;
            var r = Math.random();
            var src = './final_files/cloud.png';
            ( function( img ) { img.addEventListener( 'load', function() {
                img.style.opacity = .8;
            } ) } )( cloud );
            cloud.setAttribute( 'src', src );
            cloud.className = 'cloudLayer';

            var x = 256 - ( Math.random() * 512 );
            var y = 256 - ( Math.random() * 512 );
            var z = 100 - ( Math.random() * 200 );
            var a = Math.random() * 360;
            var s = .25 + Math.random();
            x *= .2; y *= .2;
            cloud.data = {
                x: x,
                y: y,
                z: z,
                a: a,
                s: s,
                speed: .1 * Math.random()
            };
            var t = 'translateX( ' + x + 'px ) translateY( ' + y + 'px ) translateZ( ' + z + 'px ) rotateZ( ' + a + 'deg ) scale( ' + s + ' )';
            cloud.style.webkitTransform = t;
            cloud.style.MozTransform = t;
            cloud.style.oTransform = t;

            div.appendChild( cloud );
            layers.push( cloud );
        }

        return div;
    }

    window.addEventListener( 'mousewheel', onContainerMouseWheel );
    window.addEventListener( 'DOMMouseScroll', onContainerMouseWheel );
    window.addEventListener( 'mousemove', onMouseMove );
    window.addEventListener( 'touchmove', onMouseMove );

    function onMouseMove ( e ) {

        var x = e.clientX || e.touches[ 0 ].clientX;
        var y = e.clientY || e.touches[ 0 ].clientY;

        worldYAngle = -( .5 - ( x / window.innerWidth ) ) * 180;
        worldXAngle = ( .5 - ( y / window.innerHeight ) ) * 180;
        updateView();
        event.preventDefault();

    }

    function onContainerMouseWheel( event ) {

        event = event ? event : window.event;
        d = d - ( event.detail ? event.detail * -5 : event.wheelDelta / 8 );
        updateView();
        event.preventDefault();

    }

    function generate() {

        objects = [];

        if ( world.hasChildNodes() ) {
            while ( world.childNodes.length >= 1 ) {
                world.removeChild( world.firstChild );
            }
        }

        for( var j = 0; j < 5; j++ ) {
            objects.push( createCloud() );
        }

    }

    function updateView() {
        var t = 'translateZ( ' + d + 'px ) rotateX( ' + worldXAngle + 'deg) rotateY( ' + worldYAngle + 'deg)';
        world.style.webkitTransform = t;
        world.style.MozTransform = t;
        world.style.oTransform = t;
    }

    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 ) rotateZ( ' + layer.data.a + 'deg ) scale( ' + layer.data.s + ')';
            layer.style.webkitTransform = t;
            layer.style.MozTransform = t;
            layer.style.oTransform = t;
        }

        requestAnimationFrame( update );

    }

    update();

    </script>

    

</body></html>


白云的png照片(自己下载保存):

使用时将var src = './final_files/cloud.png';换成白云的照片

Last modification:September 26th, 2019 at 08:59 pm
如果觉得我的文章对你有用,请随意赞赏