Translate

2020年6月4日 星期四

用 CSS 畫 3D 物件

第一次看到只用 CSS 就可以畫 3D 物件覺得非常神奇, 想當初在設計導航程式時,在那記憶體只有 16M 的機器上,要跑全台灣/全中國地圖,而且導航除了需要旋轉地圖到行進方向,最好還要有 2.5D 的斜角視圖才可以看到遠方更多的資訊,當時所有計算全部自己寫,光旋轉就要一堆 sin , cos 的計算,而現在只要一行指令就可以旋轉了......

3D 的基礎,投影面和視點, 在 CSS 上也必須分層建立好

首先先建一個 view 裡面指定投影距離 perspective:400px; 在 view 裡頭建立 camera , 這裡有個很重要的參數 transform-style: preserve-3d; 然後在 camera 裡再建立 obj , 同樣的這裡也必須加上 transform-style: preserve-3d; ,然後就可以在 obj 中放面的資料

最簡單的範例如下 :


<style>
#a06040
{
  width:300px;
  height:300px;
}
#a06040 .view
{
  position:absolute;
  perspective:400px;
  width:300px;
  height:300px;
  perspective-origin:50% 50%;
}
#a06040 .camera
{
  position:absolute;
  width:300px;
  height:300px;
  transform-style: preserve-3d;
}
#a06040 .obj
{
  position:absolute;
  width:300px;
  height:300px;
  transform-style: preserve-3d;
  transform:translate3d(0px,0px,-400px);
}
#a06040 .aa
{
  position:absolute;
  width:300px;
  height:300px;
  background-color:#aaaaff;
}
#a06040 .aa1
{
  transform:translate3d(150px,0,0) rotate3d(0,1,0,-60deg);
}
#a06040 .aa2
{
  transform:translate3d(-150px,0,0) rotate3d(0,1,0,60deg);
}
</style>

<div id="a06040">
<div class="view">
  <div class="camera">
    <div class="obj">
      <div class="aa aa1"></div>
      <div class="aa aa2"></div>
    </div>
  </div>
</div>
</div>


如果只有一個物件,可以省略 obj 那一層,也就是每個面都是一個物件的概念, 例如以下範例,利用八個面組成一顆鑽石


<style>
#a06043 .view
{
  font-size:6em;
  position:relative;
  perspective:3em;
  width:2em;
  height:2em;
  perspective-origin:50% 50%;
  transform:scaley(1);
  border:1px solid #000;
}
#a06043 .camera
{
  position:absolute;
  width:2em;
  height:2em;
  transform-style: preserve-3d;
  animation:a06043_a2 2s 0s linear infinite;
  transform:
    translate3d(0px,0px,-3em)
    rotatey(-0deg);
}
@keyframes a06043_a2
{
  0%
  {
    transform:
    translate3d(0px,0px,-3em)
    rotatey(0deg);
  }
  50%
  {
    transform:
    translate3d(0px,0px,-8em)
    rotatey(180deg);
  }
  100%
  {
    transform:
    translate3d(0px,0px,-3em)
    rotatey(360deg);
  }
}
#a06043 .aa
{
  position:absolute;
  transform-style: preserve-3d;
  width:0em;
  height:0em;
  border-width:0em 1em 2em 1em;
  border-style:solid;
  border-color:transparent transparent #f00 transparent;
  opacity:0.9;
  transform:
    translate3d(0,0em,1em)
    rotatex(45deg)
    translate3d(0,-1em,0em);
}
#a06043 .a1
{
  border-color:transparent transparent #ff8888 transparent;
  transform:
    rotatey(0deg)
    translate3d(0,0em,1em)
    rotatex(30deg)
    translate3d(0,-1em,0em);
}
#a06043 .a2
{
  border-color:transparent transparent #ffff88 transparent;
  transform:
    rotatey(90deg)
    translate3d(0,0em,1em)
    rotatex(30deg)
    translate3d(0,-1em,0em);
}
#a06043 .a3
{
  border-color:transparent transparent #8888ff transparent;
  transform:
    rotatey(180deg)
    translate3d(0,0em,1em)
    rotatex(30deg)
    translate3d(0,-1em,0em);
}
#a06043 .a4
{
  border-color:transparent transparent #88ff88 transparent;
  transform:
    rotatey(-90deg)
    translate3d(0,0em,1em)
    rotatex(30deg)
    translate3d(0,-1em,0em);
}
#a06043 .a5
{
  border-color:transparent transparent #ff88ff transparent;
  transform:
    scaley(-1)
    rotatey(0deg)
    translate3d(0,0em,1em)
    rotatex(30deg)
    translate3d(0,-1em,0em);
}
#a06043 .a6
{
  border-color:transparent transparent #ffaa88 transparent;
  transform:
    scaley(-1)
    rotatey(90deg)
    translate3d(0,0em,1em)
    rotatex(30deg)
    translate3d(0,-1em,0em);
}
#a06043 .a7
{
  border-color:transparent transparent #88ffff transparent;
  transform:
    scaley(-1)
    rotatey(180deg)
    translate3d(0,0em,1em)
    rotatex(30deg)
    translate3d(0,-1em,0em);
}
#a06043 .a8
{
  border-color:transparent transparent #00ff88 transparent;
  transform:
    scaley(-1)
    rotatey(-90deg)
    translate3d(0,0em,1em)
    rotatex(30deg)
    translate3d(0,-1em,0em);
}
</style>

<div id="a06043">
<div class="view">
  <div class="camera">
    <div class="aa a1"></div>
    <div class="aa a2"></div>
    <div class="aa a3"></div>
    <div class="aa a4"></div>
    <div class="aa a5"></div>
    <div class="aa a6"></div>
    <div class="aa a7"></div>
    <div class="aa a8"></div>
  </div>
</div>
</div>


掌握這個方式後,就可以開始玩 3D 了,這裡寫了一個有趣的展示,只用 CSS 就可以做出早期德軍總部的隧道畫面,這裡用了一個參數 backface-visibility: hidden;-webkit-backface-visibility: hidden; 可以把背對的面隱藏,在這個範例中我有偷懶一下,並沒有把所有的面都設到正確的方向,只有一個 aa2 那個面需要在背對時隱藏。


<style>
#a06041 .window
{
  position:relative;
  width:300px;
  height:300px;
  overflow:hidden;
  border:1px solid;
  background-color:#aaffff;
}
#a06041 .view
{
  position:absolute;
  perspective:400px;
  width:300px;
  height:300px;
  perspective-origin:50% 50%;
}
#a06041 .camera
{
  position:absolute;
  width:300px;
  height:300px;
  transform-style: preserve-3d;
  animation:a06041_a1 5s 0s linear infinite;
  transform:
    translate3d(0px,0px,calc(150px)) 
    rotate3d(0,1,0,90deg)
    translate3d(0px,0px,calc(150px + 600px));
}
@keyframes a06041_a1
{
  0%
  {
    transform:
      translate3d(0px,0px,0) 
      rotate3d(0,1,0,0deg)
      translate3d(0px,0px,0);
  }
  20%
  {
    transform:
      translate3d(0px,0px,0) 
      rotate3d(0,1,0,20deg)
      translate3d(0px,0px,100px);
  }
  40%
  {
    transform:
      translate3d(0px,0px,0) 
      rotate3d(0,1,0,-20deg)
      translate3d(0px,0px,200px);
  }
  70%
  {
    transform:
      translate3d(0px,0px,calc(150px)) 
      rotate3d(0,1,0,20deg)
      translate3d(0px,0px,calc( 600px));
  }
  90%
  {
    transform:
      translate3d(0px,0px,calc(150px)) 
      rotate3d(0,1,0,80deg)
      translate3d(0px,0px,calc(100px + 600px));
  }
  100%
  {
    transform:
      translate3d(0px,0px,calc(150px)) 
      rotate3d(0,1,0,90deg)
      translate3d(0px,0px,calc(150px + 600px));
  }
}
#a06041 .aa
{
  position:absolute;
  width: 300px;
  height:300px;
  background-image:url(https://yiharng.github.io/bird.jpg);
  background-size:300px 300px;
  transform-origin:150px 150px;
}
#a06041 .aa1
{
  width: 600px;
  transform:
    rotate3D(0,1,0,90deg)
    translate3d(150px,0px,150px);
}
#a06041 .aa2
{
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  width: 900px;
  transform:
    rotate3D(0,1,0,90deg)
    translate3d(150px,0px,-150px)
    ;
}
#a06041 .aa3
{
  height:900px;
  transform:
    rotate3D(1,0,0,-90deg)
    translate3d(0px,150px,-150px)
    ;
}
#a06041 .aa4
{
  height:900px;
  transform:
    rotate3D(1,0,0,-90deg)
    translate3d(0px,150px,150px)
    ;
}
#a06041 .obj
{
  position:absolute;
  width:300px;
  height:300px;
  transform-style: preserve-3d;
}
#a06041 .obj1
{
  position:absolute;
  width:300px;
  height:300px;
  transform-style: preserve-3d;
  transform:
    translate3d(150px,00px,calc( -900px + 150px) )
    rotate3D(0,1,0,-90deg)
    ;
}
</style>

<div id="a06041">
<div class="window">
<div class="view">
  <div class="camera">
    <div class="obj">
      <div class="aa aa1"></div>
      <div class="aa aa2"></div>
      <div class="aa aa3"></div>
      <div class="aa aa4"></div>
    </div>
    <div class="obj1">
      <div class="aa aa1"></div>
      <div class="aa aa2"></div>
      <div class="aa aa3"></div>
      <div class="aa aa4"></div>
    </div>
  </div>
</div>
</div>
</div>

加上 javascript 產生面, 可以做個球, 只要花點時間,光靠 CSS 就可以做出旋轉地球了呢 !


<style>
#a06042 .view
{
  position:relative;
  perspective:400px;
  width:300px;
  height:300px;
  perspective-origin:50% 50%;
  transform:scale(1);
}
#a06042 .camera
{
  position:absolute;
  width:300px;
  height:300px;
  transform-style: preserve-3d;
  transform:
    translate3d(0px,0px,0px)
    rotate3d(0,1,0,0deg);
}
#a06042 .obj
{
  position:absolute;
  width:300px;
  height:300px;
  transform-style: preserve-3d;
  animation:a06042_a2 10s 0s linear infinite;
  transform:
    translate3d(0px,0px,-400px)
    rotate3d(1,0,0,0deg)
    rotate3d(0,1,0,0deg);
}
@keyframes a06042_a2
{
  0%
  {
    transform:
    translate3d(0px,0px,-400px)
      rotatex(0deg)
      rotatey(0deg);
  }
  100%
  {
    transform:
    translate3d(0px,0px,-400px)
      rotatex(360deg)
      rotatey(720deg);
  }
}
#a06042 .bb
{
  position:absolute;
  width:60px;
  height:60px;
  left: calc(150px - 30px); 
  top: calc(150px - 30px); 
  background-color:#faa;
  border:2px solid #000;
  opacity:1;
  transform:
    rotate3D(0,1,0,50deg)
    rotate3D(1,0,0,30deg)
    translate3d(0px,0px,-250px);
}
</style>

<div id="a06042">
<div class="view">
  <div class="camera">
    <div class="obj">
    </div>
  </div>
</div>
</div>

<script>
(()=>
{
  let i,j;
  let b;

  for (j=-4;j<5;j++)
  for (i=0;i<12;i++)
  {
    b=$("<div class='bb'></div>");
    b.css("transform",
       "rotate3D(0,1,0,"+(i*30)+"deg) "
      +"rotate3D(1,0,0,"+(j*20)+"deg) "
      +"translate3d(0px,0px,250px)");
    $("#a06042 .obj").append(b);
  }
})();
</script>


因為 div 是矩形, 雖然可以利用 clip-path 來做裁切,但是裡頭的貼圖並不會跟著變形,如果把解析度提高,那麼每個面都會接近矩形,效果很不錯,只是效能會很差,在調整了一下適當大小,又用了些偷懶的方式,沒有精算的很準確, 最後附上旋轉五色鳥做為這篇的結束,


<style>
#a06045 .view
{
  position:relative;
  perspective:400px;
  width:300px;
  height:300px;
  perspective-origin:50% 50%;
  transform:scale(1);
}
#a06045 .camera
{
  position:absolute;
  width:300px;
  height:300px;
  transform-style: preserve-3d;
  transform:
    translate3d(0px,0px,0px)
    rotate3d(0,1,0,0deg);
}
#a06045 .obj
{
  position:absolute;
  width:300px;
  height:300px;
  transform-style: preserve-3d;
  animation:a06045_a2 10s 0s linear infinite;
  transform:
    translate3d(0px,0px,-400px)
    rotate3d(1,0,0,0deg)
    rotate3d(0,1,0,0deg);
}
@keyframes a06045_a2
{
  0%
  {
    transform:
    translate3d(0px,0px,-400px)
      rotatey(0deg);
  }
  100%
  {
    transform:
    translate3d(0px,0px,-400px)
      rotatey(360deg);
  }
}
#a06045 .bb
{
  position:absolute;
  width:60px;
  height:50px;
  left: calc(150px - 30px); 
  top: calc(150px - 25px); 
  background-color:#faa;
  border:0px solid #000;
  opacity:1;
  transform:
    rotate3D(0,1,0,50deg)
    rotate3D(1,0,0,30deg)
    translate3d(0px,0px,-250px);
}
</style>

<div id="a06045">
<div class="view">
  <div class="camera">
    <div class="obj">
    </div>
  </div>
</div>
</div>

<script>
(()=>
{
  let i,j;
  let b;

  let xx,yy;
  let degy;

  degy=11;
  xx=16;

  for (j=-5;j<6;j++)
  for (i=0;i<xx;i++)
  {
    k=-j;

    b=$("<div class='bb'></div>");

    let r=250*Math.cos(k*degy*3.14/180)*2*3.14/xx*1.06;
    b.css("width",r);
    b.css("left","calc(150px - "+(r/2)+"px)");
    b.css("background-image","url(https://yiharng.github.io/bird.jpg)");

    let sizey=r*0.4*xx;

    b.css("background-size",(r*xx)+"px "+sizey+"px ");
    b.css("background-position"
      ,(r*(xx-i))+"px "+(sizey*(6-j)/13+50)+"px");

    b.css("transform",
       "rotate3D(0,1,0,"+(i*(360/xx))+"deg) "
      +"rotate3D(1,0,0,"+(k*degy)+"deg) "
      +"translate3d(0px,0px,250px)");
    $("#a06045 .obj").append(b);
  }
})();
</script>

沒有留言:

張貼留言