使用 canvas 画布来操控图形或者图片的二维变化的方式一般是平移、缩放、和旋转。canvas 已相应提供了相关的 api。如:translate(), scale() 和 rotate(),但这些功能无法统一处理并且无法描述当前画布的处于某种状态。为了实现这种统一连贯性的状态描述,canvas 给我们提供了另外一种思路及方法,即使用:transform() / setTransform();因为画布上的每个对象都拥有一个当前的 3 x 3 变换矩阵,都可以使用一个 3 x 3 矩阵来描述当前状态,其实是 2 x 3 矩阵,但为了便于计算,人为添加第三行 0 0 1 变成 3 x 3 矩阵。费话不多话,下面直接看矩阵变换的已封装好的功能,为了便于读者理解,每一步都有详细解释及介绍。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
function createMatrix()
{
    /**
     * 初始一个矩阵,开始时,a, d 为 1,其它为 0
     * a  c  e
     * b  d  f
     * 0  0  1
     *
     * a	水平缩放绘图
     * b	水平倾斜绘图
     * c	垂直倾斜绘图
     * d	垂直缩放绘图
     * e	水平移动绘图
     * f	垂直移动绘图
     *
     * 1  0  0
     * 0  1  0
     * 0  0  1
     *
     * */
    let m = []
    m[0] = 1
        m[1] = 0
        m[2] = 0
        m[3] = 1
        m[4] = 0
        m[5] = 0
        return m
}
function multiply(m1, m2)
{
    // 左乘
    /**
     * 已知矩阵 A 和矩阵 B,求 A 和 B 的乘积 C= AB
     * 常规方法:矩阵 C 中每一个元素 Cij = A 的第 i 行 乘以(点乘) B 的第 j 列
     *
     * m1[0]  m1[2]  m1[4]     m2[0]  m2[2]  m2[4]     m[0]  m[2]  m[4]
     * m1[1]  m1[3]  m1[5]  X  m2[1]  m2[3]  m2[5]  =  m[1]  m[3]  m[5]
     *  0      0      1          0      0      1        0      0     1
     *
     *
     * */
    let m = []
    m[0] = m1[0] * m2[0] + m1[2] * m2[1] + m1[4] * 0
        m[2] = m1[0] * m2[2] + m1[2] * m2[3] + m1[4] * 0
        m[4] = m1[0] * m2[4] + m1[2] * m2[5] + m1[4] * 1
        m[1] = m1[1] * m2[0] + m1[3] * m2[1] + m1[5] * 0
        m[3] = m1[1] * m2[2] + m1[3] * m2[3] + m1[5] * 0
        m[5] = m1[1] * m2[4] + m1[3] * m2[5] + m1[5] * 1
        return m
}
function translate(m, v)
{ // v 是个二维向量,即一个数组 [x, y],向量 v 是用于表示 x y 轴方向移动的距离,及其可表示移动的方向
    let arr = [...m]
    arr[4] = arr[4] + v[0]
        arr[5] = arr[5] + v[1]
        return arr
}
function rotate(m, rad)
{
    /**
     * 假如 A(X, Y) 点初始角度为 a,绕圆点旋转 θ 度,在坐标系上可得 B 点的坐标(X', Y'),算出半径为 r = √(X2 + Y2)
     * X' = cos(a + θ) * r
     * Y' = sin(a + θ) * r
     * 根据三角函数公式:cos(α + β) = cosαcosβ - sinαsinβ
     * 可得 X' = r * cosa * cosθ – r * sina * sinθ =  X * cosθ – Y * sinθ
     * 根据三角函数公式:sin(α + β) = sinαcosβ + cosαsinβ
     * 可得 Y' = sinacosθ * r + cosasinθ * r =  X * sinθ + Y * cosθ
     * X’       cosθ    -sinθ   0       X
     * Y’   =   sinθ    cosθ    0   X   Y
     * 1         0       0      1       1
     * 根据结合律结果应为:
     *
     * cosθ    -sinθ   0       m[0]  m[2]  m[4]      arr[0]  arr[2]  arr[4]
     * sinθ    cosθ    0   X   m[1]  m[3]  m[5]  =   arr[1]  arr[3]  arr[5]
     *  0       0      1       0      0      1         0       0        1
     *
     */
    return multiply(
        [
            Math.cos(rad),
            Math.sin(rad),
            -1 * Math.sin(rad),
            Math.cos(rad),
            0,
            0
        ],
        m)
}
function scale(m, v)
{ // v 是个二维向量,即一个数组 [scaleX, scaleY],向量 v 是用于表示 x y 方向的伸缩程度
    /**
     * 假如 X,Y 分别缩放(a, b)倍
     * X' = X * a
     * Y' = Y * b
     *
     * a    0    0       m[0]  m[2]  m[4]      arr[0]  arr[2]  arr[4]
     * 0    b    0   X   m[1]  m[3]  m[5]  =   arr[1]  arr[3]  arr[5]
     * 0    0    1       0      0      1         0       0        1
     *
     */
    return multiply([v[0], 0, 0, v[1], 0, 0], m)
}
function invert(m)
{
    // 矩阵求逆
    /**
     * 待定系数法求逆
     * X' = X * a
     * Y' = Y * b
     *
     * m[0]  m[2]  m[4]      arr[0]  arr[2]  arr[4]       1   0   0
     * m[1]  m[3]  m[5]  *   arr[1]  arr[3]  arr[5]   =   0   1   0
     * 0      0      1         0       0        1         0   0   1
     *
     * m[0] * arr[0] + m[2] * arr[1] + m[4] * 0 = 1    => arr[0] = (1 - m[2] * arr[1]) / m[0]
     * m[0] * arr[2] + m[2] * arr[3] + m[4] * 0 = 0    => arr[2] = (0 - m[2] * arr[3]) / m[0]
     * m[0] * arr[4] + m[2] * arr[5] + m[4] * 1 = 0    => arr[4] = (0 - m[2] * arr[5] - m[4]) / m[0]
     * m[1] * arr[0] + m[3] * arr[1] + m[5] * 0 = 0    => arr[0] = (0 - m[3] * arr[1]) / m[1]
     * m[1] * arr[2] + m[3] * arr[3] + m[5] * 0 = 1    => arr[2] = (1 - m[3] * arr[3]) / m[1]
     * m[1] * arr[4] + m[3] * arr[5] + m[5] * 1 = 0    => arr[4] = (0 - m[3] * arr[5] - m[5]) / m[1]
     */
    let arr = []
    arr[1] = 1 / m[0] / (m[2] / m[0] - m[3] / m[1])
        arr[0] = 0 - (m[3] * arr[1]) / m[1]
        arr[3] = 1 / m[1] / (m[3] / m[1] - m[2] / m[0])
        arr[2] = 0 - (m[2] * arr[3]) / m[0]
        arr[5] = (m[4] / m[0] - m[5] / m[1]) / (m[3] / m[1] - m[2] / m[0])
        arr[4] = 0 - m[3] * arr[5] - m[5] / m[1]
        return arr
}

使用上面的矩阵函数功能来操控画布画图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var screenWidth = $(window).width() > 750 ? 750 : $(window).width()
var screenHeight = $(window).height()
var c = document.getElementById('myCanvas_1')
c.setAttribute('width', screenWidth)
c.setAttribute('height', screenHeight)
var ctx = c.getContext('2d')
var m = createMatrix()
ctx.fillStyle = 'red'
ctx.fillRect(100, 100, 250, 100)
// translate
m = translate(m, [30, 10]) // 平移 30 10
ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5])
ctx.fillStyle = 'orange'
ctx.fillRect(100, 100, 250, 100)
// rotate
m = rotate(m, (30 * Math.PI) / 180) // 旋转 30 度
ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5])
ctx.fillStyle = 'yellow'
ctx.fillRect(100, 100, 250, 100)
// scale
m = scale(m, [0.7, 0.5]) // 缩放 0.7, 0.5
ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5])
ctx.fillStyle = 'green'
ctx.fillRect(100, 100, 250, 100)
// invert 求逆
m = invert(m)
ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5])
ctx.fillStyle = 'blue'
ctx.fillRect(100, 100, 250, 100)