好耶!今天進入最後一個章節 - 讓網頁動起來。離完賽越來越近,有種雖然還在遠方,但終於看得見終點的感覺!
今天想先來介紹 transform,一個非常常用,但又不趕說完全摸透的老朋友。
transform 最大的特點就是它只改變「畫出來的位置」,不影響其他元素的排版。元素被放大或位移後,旁邊的元素不會被推開,原本的位置也還是留著。而且不改變排版,也就意味著瀏覽器處理起來比較輕鬆。像是同樣是移動元素位置,transform 的 translate()、scale() 對網頁的負擔小於直接操作 left、top、width 等屬性。
這麼好用、常用的屬性,不好好了解一下不行。那我們開始吧!
transform 本身不是單一效果,而是一個「函式清單」。依個人常用程度排序,分別有:
translate() 位移.cup {
transform: translate(20px, 10px); /* 往右 20px、往下 10px */
}
px、%、vw 等,以元素目前所在的位置為起點移動。0,等於只做水平移動:translate(20px) 等同於 translateX(20px)。💡
translate的%是相對自己:translateX(-50%)指的是「往左移動自己寬度的一半」,不是父層的一半。這也是置中的常見寫法position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%)能作用的原因。
scale() 縮放.cup {
transform: scale(1.2); /* 寬高都放大 1.2 倍 */
transform: scale(1.5, 0.8); /* 寬放大 1.5 倍、高縮成 0.8 倍 */
}
1 放大、介於 0 到 1 縮小。translate() 不同:translate() 只填一個參數是只動水平,scale() 則是兩個方向一起動。scaleX(-1) 就是水平翻轉。rotate() 旋轉.cup {
transform: rotate(15deg); /* 順時針旋轉 15 度 */
}
deg、turn(一圈)、rad。正值為順時針,負值為逆時針。transform-origin)為基準旋轉,預設是元素的正中心,下面會詳細說明。skew() 傾斜.cup {
transform: skew(10deg, 0); /* 水平方向傾斜 10 度 */
}
skewX),參數 2 是沿垂直方向傾斜(skewY),省略第二個參數時預設為 0。上面的函式大多有指定軸向的版本:
translateX()、scaleX()、skewX()、rotateX()
translateY()、scaleY()、skewY()、rotateY()
translateZ()、scaleZ()、rotateZ()
translate3d(x, y, z)、scale3d(x, y, z)、rotate3d(x, y, z, 角度)
💡 注意:
rotate()本身就是繞 Z 軸旋轉,所以rotate(15deg)等同於rotateZ(15deg);rotateX()和rotateY()則是讓元素「翻」起來,屬於 3D 變形。skew沒有 Z 軸版本,只有skewX()與skewY()。
matrix():所有 2D 變形的底層,用六個數字 matrix(a, b, c, d, e, f) 組合出位移、縮放、旋轉、傾斜。基本上不會手寫,通常是在 DevTools 裡看到瀏覽器換算後的結果。3D 的對應版本是 matrix3d(),有 16 個數字。perspective():設定「觀察者離畫面有多遠」,也就是場景深度,數值越小,3D 的透視感越強。需要搭配 rotateX()、rotateY()、translateZ() 這類 3D 變形才看得出效果,而且要寫在 transform 清單的最前面。實際應用可以參考這篇文章。同一個 transform 可以串接多個函式,用空白分隔,要注意函式是由左到右,一一對元素作用:
.a { transform: translateX(100px) rotate(45deg); }
.b { transform: rotate(45deg) translateX(100px); }
這兩行看起來只是換了位置,結果卻不一樣:
.a:先把座標系往右移 100px,再在新位置旋轉 45 度。.b:先旋轉,座標軸跟著轉了 45 度,接著 translateX(100px) 就會沿著斜 45 度的方向移動。transform-origin 決定從哪個點動rotate 和 scale 都需要一個「基準點」。預設是元素的正中心(50% 50%),但很多時候我們要的不是中心。
想像一個招手動作,繞手臂中心轉,會像整條手臂在空中轉圈;以肩膀為基準才像在招手。這時候就要改 transform-origin:
.arm {
transform-origin: 85% 85%; /* 上臂根部位置(水平 85%、垂直 85%) */
transform: rotate(-8deg);
}
值可以用關鍵字(top、left、center、bottom),也可以用百分比或長度。
💡 如果是對 inline SVG 內的元素(例如
<path>、<g>)下transform,要特別注意:預設的基準點不是元素中心,而是整個 SVG 畫布的左上角。要改成以元素自己為準,需要搭配transform-box: fill-box與transform-origin: center;。
聽起來超像廢話😂
但真的很容易踩到,transform 本質是「一整個屬性」,後寫的會蓋掉先寫的,不會疊加。
.cup {
transform: translateX(20px);
}
.cup:hover {
transform: rotate(10deg); /* translateX(20px) 會被覆蓋不見 */
}
想要兩個效果同時存在,必須在 hover 時把兩個都寫上:transform: translateX(20px) rotate(10deg)。我最常在 @keyframes (動畫影格)設定時遇到:
transform,套用動畫後才發現衝突。好在貼心的 CSS 來把最常用的函式拆成獨立屬性:translate、rotate、scale,三者互不影響,還能與 transform 並存,完美解決覆蓋,寫法也更直白。
.cup {
translate: 20px 0;
}
.cup:hover {
rotate: 10deg; /* translate 還在,兩個效果同時存在 */
}
阿罵你怎麼沒有感覺? 👵🚲👧💦
<span>、<a> 這種預設是 display: inline 的行內元素,transform 不會生效。要改成 inline-block、block 或是 flex/grid 的子項目,這類能形成方框的元素。畫面渲染時才能運算出對應的變形與位移。
套用 transform 的元素,會建立新的 stacking context,也會成為內部 position: fixed 子元素的定位基準。像是在這個 Codepen 範例 中,能看到 modal 已經下 z-index: 9999;,但因為外層卡片下了 transform 而產生了新的定位基礎。
這也能說明為什麼像 Ant Design 要用 Portal 掛到 body,Bootstrap 文件也建議把 modal 放在頁面最上層。 如果發現 z-index 突然失效,或是 fixed 的彈窗跟著父層跑,可以檢查一下外層是否有用到 transform。
💡 其他屬性補充
- 建立 stacking context:
opacity(不為 1)、filter、will-change(指定特定屬性時)都會。- 成為
position: fixed子元素的定位基準:transform、filter、perspective、will-change: transform等會。