点击查看html编辑器说明文档

漂浮的3D方块,很适合做背景edit icon

|
|
Fork(复制)
|
|
作者:
穿越者X57

👉 新版编辑器已上线,点击进行体验吧!

BUG反馈
嵌入
设置
下载
HTML
格式化
支持Emmet,输入 p 后按 Tab键试试吧!
<head> ...
展开
</head>
<body>
            
            
<div id="cube-container"></div>
  <template id="cube-template">
    <div class="cube">
      <div class="shadow"></div>
      <div class="sides">
        <div class="back"></div>
        <div class="top"></div>
        <div class="left"></div>
        <div class="front"></div>
        <div class="right"></div>
        <div class="bottom"></div>
      </div>
    </div>
  </template>
        
</body>
SCSS
格式化
            
            .cubes
{
  .cube
  {
    position: absolute;
    height: 100px;
    width: 100px;
    margin: 0;

    animation: cube-fade-in 2s cubic-bezier(.165, .84, .44, 1);
    will-change: transform;

    @keyframes cube-fade-in
    {
      0%
      {
        opacity: 0;
        transform: scale(.5)
      }
    }

    *
    {
      position: absolute;
      height: 100%;
      width: 100%;
    }

    .shadow
    {
      background: #07427a;
      top: 40%;
    }

    .sides
    {
      transform-style: preserve-3d;
      perspective: 600px;

      div
      {
        backface-visibility: hidden;
        will-change: transform;
      }

.front {
  transform: rotateY(0deg) translateZ(50px);
}
.back {
  transform: rotateY(-180deg) translateZ(50px);
}
.left {
  transform: rotateY(-90deg) translateZ(50px);
}
.right {
  transform: rotateY(90deg) translateZ(50px);
}
.top {
  transform: rotateX(90deg) translateZ(50px);
}
.bottom {
  transform: rotateX(-90deg) translateZ(50px);
}

    }
  }
}
        
JS
格式化
            
            "use strict";

// Utility functions
var utils = {
  // Random float between min and max
  random: function(min, max) {
    return Math.random() *  (max - min) + min;
  },
  // Returns a random item from an array
  arrayRandom: function(arr) {
    return arr[ Math.floor( Math.random() * arr.length ) ];
  },
  // Linear interpolation between a and b
  // Ex: (100, 200, 0.5) = 150
  interpolate: function(a, b, i) {
    return a*(1-i) + b*i;
  },
  // querySelectorAll as an array
  queryArray: function(selector, node) {
    if (!node) node = document.body;
    return Array.prototype.slice.call(node.querySelectorAll(selector));
  }
};

// =======
  // helpers
  // =======

//state is state of rotation (angle) for x and y
  const setState = (state, speed) =>
    directions.forEach(axis => {
      state[axis] += speed[axis];
      // console.log('statex: ' + state.x);
      // console.log('statey ' + state.y);
      if (Math.abs(state[axis]) < 360) return;
      const max = Math.max(state[axis], 360);
      const min = max == 360 ? Math.abs(state[axis]) : 360;
      state[axis] = max - min;
      // console.log('statex: ' + state.x);
    });

  const cubeIsHidden = left => left > parentWidth + 30;
// var cubeIsHidden = function cubeIsHidden(left) {
//   return left > parentWidth + 30;
// };


  // =================
  // shared variables
  // =================

  const template = document.getElementById("cube-template");

  const parent = document.getElementById("cube-container");
  // get width of cube container
  const getParentWidth = () => parent.getBoundingClientRect().width;
  let parentWidth = getParentWidth();

  // update parent width on resize
  window.addEventListener("resize", () => parentWidth = getParentWidth());

  const directions = ["x", "y"];
  
// data for colours and sizes
  const palette = {
    white: {
      color: [255, 255, 255],
      shading: [160, 190, 218]
    },
    orange: {
      color: [255, 250, 230],
      shading: [255, 120, 50]
    },
    green: {
      color: [205, 255, 204],
      shading: [0, 211, 136]
    }
  };

  const sizes = {
    xs: 15,
    s: 25,
    m: 40,
    l: 100,
    xl: 120
  };

  const testing = () => {
    console.log('testing')
  }
  testing();

  // ==============
  // cube instances
  // ==============

  const setCubeStyles = ({cube, size, left, top}) => {
    // assigning size and position values
    Object.assign(cube.style, {
      width: `${size}px`,
      height: `${size}px`,
      left: `${left}px`,
      top: `${top}px`
    });

    Object.assign(cube.querySelector(".shadow").style, {
      // assigning shadow blur and opacity value according to size
      filter: `blur(${Math.round(size * .6)}px)`,
      opacity: Math.min(size / 120, .4)
    });
  };

  const createCube = size => {
    // The Document method importNode() creates a new copy of the specified Node or DocumentFragment from another document so that it can be inserted into the current Document. It is not yet included in the document tree; to do that, you need to call a method such as appendChild() or insertBefore(). Here we are importing the html content of the cube template, and from that import, selecting the cube div
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/importNode
    const fragment = document.importNode(template.content, true);
    const cube = fragment.querySelector(".cube");
    const state = {
      x: 0,
      y: 0
    };
    
    //calculating speed of rotation
    const speed = directions.reduce((object, axis) => {
      const max = size > sizes.m ? .3 : .6;
      object[axis] = utils.random(-max, max);
      return object;
    }, {});

    const sides = utils.queryArray(".sides div", cube).reduce((object, side) => {
      object[side.className] = {
        side,
        hidden: false,
        rotate: {
          x: 0,
          y: 0
        }
      };
      return object;
    }, {});

    sides.top.rotate.x = 90;
    sides.bottom.rotate.x = -90;
    sides.left.rotate.y = -90;
    sides.right.rotate.y = 90;
    sides.back.rotate.y = -180

    return {fragment, cube, state, speed, sides: Object.values(sides)};
  };

  

  // building cubes data with palette and size
  // could be randomized?
  const cubes = [
    {
      tint: palette.green,
      size: sizes.xs,
      left: 35,
      top: 465
    },{
      tint: palette.white,
      size: sizes.s,
      left: 55,
      top: 415
    },{
      tint: palette.white,
      size: sizes.xl,
      left: 140,
      top: 400
    },{
      tint: palette.white,
      size: sizes.m,
      left: 420,
      top: 155
    },{
      tint: palette.green,
      size: sizes.xs,
      left: 440,
      top: 280
    },{
      tint: palette.orange,
      size: sizes.s,
      left: 480,
      top: 228
    },{
      tint: palette.white,
      size: sizes.l,
      left: 580,
      top: 255
    },{
      tint: palette.green,
      size: sizes.s,
      left: 780,
      top: 320
    },{
      tint: palette.white,
      size: sizes.xl,
      left: 780,
      top: 120
    },{
      tint: palette.orange,
      size: sizes.l,
      left: 900,
      top: 310
    },{
      tint: palette.green,
      size: sizes.m,
      left: 1030,
      top: 200
    }
  ].map(object => Object.assign(createCube(object.size), object));
  // for each item in the array, build a cube with a createCube method and assign it styles
  cubes.forEach(setCubeStyles);


  // =======================
  // cube rotating animation
  // =======================
// Adding initial position and rotation
  const getDistance = (state, rotate) =>
    directions.reduce((object, axis) => {
      object[axis] = Math.abs(state[axis] + rotate[axis]);
      return object;
    }, {});
  // or 
// const getDistance = (state, rotate) => {
//     return {
//         x: Math.abs(state.x + rotate.x),
//         y: Math.abs(state.y + rotate.y)
//     }
// }

// or 
// function getDistance(state, rotate) {
//     const object = {};
//     for (let axis of ['x', 'y']) {
//         object[axis] = Math.abs(state[axis] + rotate[axis]);
//     }
//     return object;
// }

// calculate the rotation value
  const getRotation = (state, size, rotate) => {
    const axis = rotate.x ? "Z" : "Y";
    const direction = rotate.x > 0 ? -1 : 1;

    return `
      rotateX(${state.x + rotate.x}deg)
      rotate${axis}(${direction * (state.y + rotate.y)}deg)
      translateZ(${size / 2}px)
    `;
  };





  const getShading = (tint, rotate, distance) => {
    // const darken = directions.reduce((object, axis) => {
    //   const delta = distance[axis];
    //   const ratio = delta / 180;
    //   object[axis] = delta > 180 ? Math.abs(2 - ratio) : ratio;
    //   return object;
    // }, {});
        const darken = {};
    for (let axis of ['x', 'y']) {
        const delta = distance[axis];
        const ratio = delta / 180;
        darken[axis] = delta > 180 ? Math.abs(2 - ratio) : ratio;
    }
    // console.log(darken);

    if (rotate.x)
      darken.y = 0;
    else {
      const {x} = distance; //look up for ES5 syntax
      if (x > 90 && x < 270)
        directions.forEach(axis => darken[axis] = 1 - darken[axis]);
    }

    const alpha = (darken.x + darken.y) / 2;
    const blend = (value, index) => Math.round(utils.interpolate(value, tint.shading[index], alpha));
    const [r, g, b] = tint.color.map(blend);

    return `rgb(${r}, ${g}, ${b})`;
  };

  const shouldHide = (rotateX, x, y) => {
    if (rotateX)
      return x > 90 && x < 270;
    if (x < 90)
      return y > 90 && y < 270;
    if (x < 270)
      return y < 90;
    return y > 90 && y < 270;
  };

  const updateSides = ({state, speed, size, tint, sides, left}) => {
    if ( cubeIsHidden(left)) return;

    const animate = object => {
      const {side, rotate, hidden} = object;
      const distance = getDistance(state, rotate);

      // don't animate hidden sides
      if (shouldHide(rotate.x, distance.x, distance.y)) {
        if (!hidden) {
          side.hidden = true;
          object.hidden = true;
        }
        return;
      }

      if (hidden) {
        side.hidden = false;
        object.hidden = false;
      }

      side.style.transform = getRotation(state, size, rotate);
      side.style.backgroundColor = getShading(tint, rotate, distance);
    };

    setState(state, speed);
    sides.forEach(animate);
  };

  const tick = () => {
    cubes.forEach(updateSides);
    requestAnimationFrame(tick);
  };


  // ===============
  // parallax scroll
  // ===============

  // give it some extra space to account for the parallax and the shadows of the cubes
//   const parallaxLimit = parent.getBoundingClientRect().height + 80;

//   window.addEventListener("scroll", () => {
//     const scroll = window.scrollY;
//     if (scroll < parallaxLimit) {
//       cubes.forEach(({cube, speed}) =>
//         cube.style.transform = `translateY(${Math.abs(speed.x * .5) * scroll}px)`);
//       return;
//     }
//   });


  // ==========
  // initialize
  // ==========

  const container = document.createElement("div");
  container.className = "cubes";
  cubes.forEach(({fragment}) => container.appendChild(fragment));

  const start = () => {
    tick();
    parent.appendChild(container);
  };

  "requestIdleCallback" in window
    ? requestIdleCallback(start)
    : start();
        
预览
控制台
清空