日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > CSS >内容正文

CSS

可视化学习:CSS transform与仿射变换

發(fā)布時(shí)間:2023/12/24 CSS 43 coder
生活随笔 收集整理的這篇文章主要介紹了 可视化学习:CSS transform与仿射变换 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

引言

在幾年前,我就在一些博客中看到關(guān)于CSS中transform的分析,講到它與線性代數(shù)中矩陣的關(guān)系,但當(dāng)時(shí)由于使用transform比較少,再加上我畢竟是個(gè)數(shù)學(xué)學(xué)渣,對(duì)數(shù)學(xué)有點(diǎn)畏難心理,就有點(diǎn)看不下去,所以只是隨便掃了兩眼,就沒有再繼續(xù)了解了。現(xiàn)在在學(xué)習(xí)可視化,又遇到了這個(gè)點(diǎn),又說到這是可視化的基礎(chǔ)知識(shí),既然這樣,那看來還是逃不過去,那就再多了解一點(diǎn)吧。

transform的作用

使用過transform的前端小伙伴一定不陌生,通過對(duì)CSS中transform屬性的設(shè)置,我們可以對(duì)DOM元素進(jìn)行縮放、旋轉(zhuǎn)、平移,以及扭曲,從而改變?cè)氐奈恢谩⑿螤睢⒋笮『徒嵌取?/p>

仿射變換

CSS中的transform對(duì)應(yīng)到圖形學(xué)中的概念就是仿射變換。

仿射變換簡單來說就是“線性變換 + 平移”。

在CSS中對(duì)某個(gè)DOM元素應(yīng)用仿射變換,可以簡單理解成是把這個(gè)元素原本的整個(gè)坐標(biāo)系進(jìn)行了變換,并且這個(gè)坐標(biāo)系的原點(diǎn)在最初始時(shí)位于DOM元素的中心,X軸朝右、Y軸朝上、Z軸朝外,也就是朝向屏幕。

所以就是說,對(duì)某個(gè)DOM元素進(jìn)行仿射變換,就相當(dāng)于對(duì)它所對(duì)應(yīng)的幾何圖形的每個(gè)頂點(diǎn)向量進(jìn)行仿射變換。

關(guān)于圖形的仿射變換,有兩個(gè)性質(zhì):

第一,仿射變換不改變直線段的形狀,也就是說,應(yīng)用仿射變換后,直線段依舊是直線段;

第二,應(yīng)用相同的仿射變換后,兩條直線段的長度比例保持不變。

平移

接下來我們先說平移,平移變換是最簡單的仿射變換。

假設(shè)存在一個(gè)向量P(x0, y0),我們想要把它沿著另一個(gè)向量Q(x1, y1)的方向移動(dòng)對(duì)應(yīng)距離,那只要將兩個(gè)向量相加,我們就可以得到這個(gè)新的向量它的坐標(biāo)。

x = x0 + x1
y = y0 + y1

這就是平移變換的公式。

線性變換

根據(jù)公式可以看出,應(yīng)用平移變換后,原始坐標(biāo)系的原點(diǎn)會(huì)發(fā)生變化。

但是應(yīng)用線性變換后,原點(diǎn)卻并不會(huì)變化;下面來講解兩個(gè)常用的線性變換:旋轉(zhuǎn)和縮放。

  • 旋轉(zhuǎn)

    首先我們先來看旋轉(zhuǎn)變換。

    假設(shè)存在一個(gè)向量P(x0, y0),長度為r,與X軸夾角為θ,現(xiàn)在將它逆時(shí)針旋轉(zhuǎn)α角,那么此時(shí)新的向量P'的坐標(biāo)x和y分別是多少呢?

    首先我們根據(jù)圓的參數(shù)方程,可以得到如下公式:

    x0 = r * cosθ
    y0 = r * sinθ
    
    x = r * cos(α+θ)
    y = r * sin(α+θ)
    

    但這樣并看不出新舊坐標(biāo)之間的關(guān)聯(lián),所以需要進(jìn)行推導(dǎo)。

    在上圖中,我們假設(shè)旋轉(zhuǎn)θ角后得到了一個(gè)新的坐標(biāo)系(藍(lán)色),此時(shí)我們可以求得向量P'在新坐標(biāo)系的坐標(biāo),此時(shí)P'在新坐標(biāo)系的坐標(biāo)可以表示為:

    x' = r * cosα -> AF
    y' = r * sinα -> AI
    

    分別相當(dāng)于是線段AF和AI的長度。

    此時(shí)依舊看不出新舊坐標(biāo)之間的關(guān)聯(lián),我們還需要繼續(xù)推導(dǎo),求出向量P'在原坐標(biāo)系的值,在上圖中相當(dāng)于我們要求出線段AJ和AK的長度。

    • 先來求AJ的長度

      首先我們從圖中可以看出AJ = AG - JG, 并且 AG = AF * cosθ

      同時(shí)JG 和LF的長度相同,DF與AI的長度相同,且角FDJ的度數(shù)也是θ,所以可以得到JG = AI * sinθ

      最終我們可以得到如下公式:

      AJ = AF * cosθ - AI * sinθ 
         = r * cosα * cosθ - r * sinα * sinθ
      

      又因?yàn)椋?/p>

      x0 = r * cosθ
      y0 = r * sinθ
      

      就可以得到AJ的長度,也就是新向量的x坐標(biāo)

      x = x0 * cosα - y0 * sinα
      
    • 接著來求AK的長度

      從圖中我們也可以看出AK = AM + MK,并且AM = AI * cosθ

      MK又可以分為MN和NK兩段,相當(dāng)于MK = AF * sinθ

      最終我們可以得到:

      AK = AI * cosθ + AF * sinθ
         = r * sinα * cosθ + r * cosα * sinθ
      

      再加上原坐標(biāo)和角度及半徑的關(guān)系,就可以得到AK的長度,也就是新向量的y坐標(biāo):

      y = x0 * sinα + y0 * cosα
      

    至此我們就得到了新坐標(biāo)和原坐標(biāo)以及旋轉(zhuǎn)角度之間的關(guān)系,也就是旋轉(zhuǎn)變換的公式:

    x = x0 * cosα - y0 * sinα
    y = x0 * sinα + y0 * cosα
    

    根據(jù)線性代數(shù)的知識(shí),我們可以使用矩陣的形式來表示以上公式:

    [x]   [cosα  -sinα]   [x0]
    | | = |           | x |  |
    [y]   [sinα   cosα]   [y0]
    
  • 縮放

    接著我們繼續(xù)看縮放變換。縮放變換相當(dāng)于是讓向量與標(biāo)量相乘。

    比如我們使X軸縮放比例為sx,使Y軸縮放比例為sy,就可以得到新向量的坐標(biāo)為:

    x = sx * x0
    y = sy * y0
    

    縮放比旋轉(zhuǎn)簡單一些,可以直接寫出矩陣形式的公式:

    [x]   [sx  0]   [x0]
    | | = |     | x |  |
    [y]   [0  sy]   [y0]
    

至此,我們就基本了解了仿射變換的公式,并且可以看出線性變換的公式可以用矩陣相乘的形式進(jìn)行表示。

除了不改變?cè)c(diǎn),線性變換還有另外一個(gè)性質(zhì),就是可以進(jìn)行疊加;多個(gè)線性變換的疊加結(jié)果就是將線性變換的矩陣依次相乘,最后再與原始向量相乘。

根據(jù)以上內(nèi)容,我們可以得到仿射變換的一般表達(dá)式:

P = M x P0 + P1

M為多個(gè)線性變換的疊加結(jié)果,也就是變換矩陣的相乘結(jié)果,P0為原始向量坐標(biāo),P1為平移。

公式優(yōu)化

為了便于計(jì)算,我們還可以對(duì)以上的仿射變換表達(dá)式進(jìn)行優(yōu)化,通過增加維度來使用矩陣進(jìn)行表示:

[P]   [M  P1]   [P0]
| | = |     | x |  |
[1]   [0   1]   [1 ]

這實(shí)際上就是給線性空間增加了一個(gè)維度,用高維度的線性變換表示了低維度的仿射變換。

這種n+1維坐標(biāo)被稱為齊次坐標(biāo),對(duì)應(yīng)的矩陣被稱為齊次矩陣

我們需要注意,由于平移變換會(huì)改變坐標(biāo)原點(diǎn),不同的變換順序很可能會(huì)導(dǎo)致不同的變換結(jié)果,所以要注意矩陣相乘的順序。

公式應(yīng)用

接下來我們就來應(yīng)用一下線性變換的公式。

假設(shè)現(xiàn)在在頁面上有一個(gè)div。

<div class="block separate">我使用分開寫</div>
.block {
  width: 100px;
  height: 100px;
  color: #fff;
  background: orange;

  &.separate {
    transform: rotate(30deg) translate(100px, 50px) scale(1.5);
  }
}

通過簡單的旋轉(zhuǎn)和平移,我們改變了元素的角度、位置和大小。

此時(shí)我們對(duì)于transform的變換是分開寫的,但在CSS的transform中,可以使用一個(gè)matrix函數(shù),讓我們對(duì)這些變換進(jìn)行合并編寫。

首先我們引入一個(gè)ogl庫,使用其中定義的矩陣類Mat3(也可以借助其他數(shù)學(xué)庫,比如mathjs):

import { Mat3 } from 'ogl';

然后針對(duì)上面的3個(gè)變換,分別定義三個(gè)變換矩陣,分別是旋轉(zhuǎn)矩陣、平移矩陣和縮放矩陣:

const rad = Math.PI / 6;

let a = new Mat3(
    // 旋轉(zhuǎn)矩陣
    Math.cos(rad), -Math.sin(rad), 0,
    Math.sin(rad), Math.cos(rad), 0,
    0, 0, 1
);
let b = new Mat3(
    // 平移矩陣
    1, 0, 100,
    0, 1, 50,
    0, 0, 1
);
let c = new Mat3(
    // 縮放矩陣
    1.5, 0, 0,
    0, 1.5, 0,
    0, 0, 1
);

// -------------
// 使用math.js
const a = math.matrix(
  [
    [Math.cos(rad), -Math.sin(rad), 0], 
    [Math.sin(rad), Math.cos(rad), 0],
    [0, 0, 1]
  ]
);
const b = math.matrix(
  [
    [1, 0, 100], 
    [0, 1, 50],
    [0, 0, 1]
  ]
);
const c = math.matrix(
  [
    [1.5, 0, 0], 
    [0, 1.5, 0],
    [0, 0, 1]
  ]
);

接著對(duì)三個(gè)矩陣進(jìn)行相乘,得到axbxc的結(jié)果:

const res = [a, b, c].reduce((prev, current) => {
  return current.multiply(prev); // prev x current 結(jié)果保存到current
});

// -------------
// 使用math.js
let res = math.multiply(a, b);
res = math.multiply(res, c);

最后我們利用CSS變量將JS的計(jì)算結(jié)果應(yīng)用到樣式上:

.block {
  // ...

  &.combine {
    --trans: none;
    transform: var(--trans);
  }
}

由于CSS的matrix是一個(gè)簡寫的齊次矩陣,它省略了三階齊次矩陣第三行的0,0,1,所以只有6個(gè)值。

const combine = document.querySelector('.combine');
const s = res.slice(0, 6);

matrix貌似是列主序,所以在設(shè)置的時(shí)候,需要按如下順序賦值:

const combine = document.querySelector('.combined');

combine.style.setProperty('--trans', `matrix(
${s[0]},${s[3]},
${s[1]},${s[4]},
${s[2]},${s[5]},
)`);


// -------------
// 使用math.js
const s = Array.from(res).map(item => item.value);
combine.style.setProperty('--trans', `matrix(
  ${s[0]},${s[3]},
  ${s[1]},${s[4]},
  ${s[2]},${s[5]}
)`);

可以明顯看出,這樣使用的效果,和rotate、translate和scale分開寫的效果是一樣的。

總結(jié)

利用仿射變換,我們可以快速繪制出形態(tài)、位置、大小各異的眾多幾何圖形,比如實(shí)現(xiàn)粒子動(dòng)畫。

也許在普通的前端開發(fā)中,用不到太多,也并不太需要說去利用matrix去減少CSS的代碼體積,但如果要去做可視化方面的開發(fā),仿射變換還是可以多去了解一下。

總結(jié)

以上是生活随笔為你收集整理的可视化学习:CSS transform与仿射变换的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。