深入理解CSS 中 transform matrix矩阵变换问题


Posted in HTML / CSS onAugust 30, 2021

一、概述

css中transform属性中的translate、scale、rotate、skew变换属性均是通过matrix矩阵变换实现的。直接使用矩阵变换,实现位移、缩放、旋转、倾斜等动画,不够直观;实际开发中还是使用变换属性多一点。但这一点都不影响matrix属性的重要性;理解matrix属性定义的参数,需要一些线性代数的基础。

二、矩阵

1、矩阵的定义

将一些元素排列成若干行,每行放上相同数量的元素,就是一个矩阵。如:

深入理解CSS 中 transform matrix矩阵变换问题

2、矩阵的基本运算

加(减)法:

两个矩阵的加减法,取每个元素的对应的和(差)

深入理解CSS 中 transform matrix矩阵变换问题

数乘:

数字与矩阵元素之间的对应的乘积

深入理解CSS 中 transform matrix矩阵变换问题

矩阵乘法:

仅当第一个矩阵的列数(column)和第二个矩阵的行数(row)相等时才能定义。运算规则是,第一个矩阵行元素 与 第二个矩阵中的列元素,分别相乘求和,得到对应元素。例如 第一个矩阵的第一行元素[1 0 2],与第二个矩阵的第一列元素[3,2,1],分别相乘求和,即:1 x 3 + 0 x 2 + 2 x 1 = 5;得到运算后矩阵的左上第一个元素。

深入理解CSS 中 transform matrix矩阵变换问题

3、向量

物理学称为矢量,指具有大小(magnitude)和方向的量。它可以形象化地表示为带箭头的线段。

向量的分解:

直角坐标系中,任意向量可表示为:其中,i、j为单位向量。

深入理解CSS 中 transform matrix矩阵变换问题

4、矩阵与向量

矩阵中的元素可以看做是一组坐标,而在平面直角坐标系中,一组向量可以使用坐标表示,因此可以使用矩阵表示一组向量,而对矩阵的运算,可以看做是对一组坐标的变换。来看具体的例子。

三、矩阵变换

1、矩阵缩放

建立一个特殊的平面直角坐标系,坐标系的特殊之处在于x轴和y轴是橡皮筋构成的,可以进行任意拉伸和收缩。坐标系中存在向量A(-1,1);考虑如何将其放大一倍。

深入理解CSS 中 transform matrix矩阵变换问题

坐标系是弹性的,显然只需要将坐标系拉伸一倍就可以使得向量A放大一倍。根据公式有:

深入理解CSS 中 transform matrix矩阵变换问题

拉伸坐标系实际上改变的是单位向量,拉伸后的坐标系单位向量均为原先的两倍,即:

深入理解CSS 中 transform matrix矩阵变换问题

通过缩放单位向量,使得坐标系中的向量发生缩放,这是对于单一向量而言的。对于页面上的由多个坐标构成的块状 div 而言,缩放单位向量最直观的效果就是长度和宽度的缩放。

2、矩阵的旋转

如何使坐标系中的向量发生旋转,考虑将向量A(-1,1),顺时针旋转45度。

深入理解CSS 中 transform matrix矩阵变换问题

很显然需要旋转单位向量。有:

深入理解CSS 中 transform matrix矩阵变换问题

结果:

深入理解CSS 中 transform matrix矩阵变换问题

对A向量旋转可以通过旋转单位向量实现,通过对单位向量的旋转实现对任意向量的旋转。常见的变换操作,如缩放,旋转,倾斜,都可以通过对单位向量的操作进行实现。css matrix函数提供的参数就是描述一组单位向量的矩阵。

四、css中的矩阵变换

css 中 transform matrix 2d变换的参数一共有6个:

matrix(a, b, c, d, e, f)

其中默认参数为:

matrix(1, 0, 0, 1, 0, 0)

其中前4个参数,就是单位向量i(1,0)、j(0,1)。但注意,web页面中的坐标系原点在左上角,向右和向下对应平面直角坐标系的x轴和y轴正向;因此与平面直角坐标系的y轴的方向是相反的。

观察 transform 属性中的scale、rotate、skew是如何通过matrix矩阵来实现。

1、scale

使用scale缩放一个div的css是这样描述的:表示将宽和高同时放大两倍。

transform: scale(2); //同transform: scale(2,2)

使用矩阵达到上述效果,如果使用matrix实现,将单位向量放大两倍即可

transform: matrix(2,0,0,2,0,0);

2、rotate

使用rotate顺时针旋转div 30deg:

transform: rotate(30deg);

使用matrix实现,只需旋转单位向量即可

 transform: matrix(cos30°, sin30°, -sin30°, cos30°, 0,  0);
  transform: matrix(0.866, 0.5, -0.5, 0.886 ,0 , 0);

3、skew

使用skew 倾斜30度:

transform: skew(30deg, 0);

使用matrix实现:

transform: matrix(1,0,0.5773502691896257,1,0,0);

4、translate

上述3中变换均通过变换单位向量产生,原点都未发生变化。位移变化需要增加矩阵元素,和函数的参数,也是matrix(a, b, c, d, e, f)中,e、f参数的作用,矩阵的形式可以变现为以下形式:

深入理解CSS 中 transform matrix矩阵变换问题

如,坐标[-1,-1],向右向上平移2个单位,得到变换后的坐标[1, 1]。

深入理解CSS 中 transform matrix矩阵变换问题

下面的css效果是一样的:

transform: translate(50px,0);
transform: matrix(1,0,0,1,50,0);

使用matrix的形式,可以一次性定义上述4种变换。但使用transform, 需要将变换表达式写在一行,使用空格分隔有助于阅读,但不是必须的。

transform: skew(30deg,0) scale(2) rotate(30deg) translate(50px);

5、matrix3d

matrix3d(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)    //定义 3D 转换,使用 16 个值的 4x4 矩阵。

矩阵的形式为:

深入理解CSS 中 transform matrix矩阵变换问题

6、2D变换的矩阵形式

给出一组transform变换(rotate、skew、translate、scale),将其转换成matrix函数的参数形式,需要注意以下几点:

1、rotate与skew变换是相互影响的,试图直接通过三角函数的计算得出相应的参数是不正确的,需要将其带入矩阵进行运算;

2、scale与translate变换理论上可以直接修改矩阵元素,也可以带入矩阵运算,但反复测试发现,translate带入矩阵后运算结果有误,这里直接修改矩阵元素。

3、矩阵运算比较复杂,使用的 Sylvester.js 库。以下是代码

let transformString = 'skew(15deg) rotate(30deg) scale(1.5) translate(30px)'
function stringToMatrix(transformString) {
    try {
        // 参数检查
        if (typeof transformString !== 'string') {
            console.error('params must be string');
            return;
        }
        if (transformString.length === 0) {
            console.error('wrong transform format');
            return
        }
        if (['X', 'Y', '3d'].some(item => transformString.includes(item))) {
            console.error('3d transform unsupported yet');
            return;
        }
        let a = 1, b = 0, c = 0, d = 1, e = 0, f = 0;
        let reg = /(scale|rotate|skew|translate){1}\(.*?\)/g;

        let matrixDefault = $M([
            [1, 0, 0],
            [0, 1, 0],
            [0, 0, 1]
        ]);
        let matrixScale, matrixRotate, matrixSkew, matrixTranslate, matrixResult;

        transformString.match(reg).forEach(item => {
            let re = /\d+(.\d+)?/g;
            if (item.includes('rotate')) {
                let params = item.match(re);
                if (params) {
                    // a = Math.cos(params[0] / 180 * Math.PI) * a; //需要带入矩阵
                    // b = Math.sin(params[0] / 180 * Math.PI) * a;
                    // c = -Math.sin(params[0] / 180 * Math.PI) * d;
                    // d = Math.cos(params[0] / 180 * Math.PI) * d;

                    const i = params[0] / 180 * Math.PI;
                    matrixRotate = $M([
                        [Math.cos(i), -Math.sin(i), 0],
                        [Math.sin(i), Math.cos(i), 0],
                        [0, 0, 1]
                    ])
                    matrixResult = matrixResult ? matrixResult.x(matrixRotate) : matrixDefault.x(matrixRotate);
                }
            }
            if (item.includes('skew')) {
                let params = item.match(re);
                // to fix 角度不可超过90度
                // params[0] ? c = Math.tan(params[0] / 180 * Math.PI) : '';
                // params[1] ? b = Math.tan(params[1] / 180 * Math.PI) : '';

                const [i = 0, j = 0] = params.map(a => parseFloat(a));
                // matrixSkew = [1, Math.tan(j), Math.tan(i), 1, 0, 0];//需要带入矩阵
                matrixSkew = $M([
                    [1, Math.tan(i / 180 * Math.PI), 0],
                    [Math.tan(j / 180 * Math.PI), 1, 0],
                    [0, 0, 1]
                ]);
                matrixResult = matrixResult ? matrixResult.x(matrixSkew) : matrixDefault.x(matrixSkew);
            }
            if (item.includes('scale')) {
                let params = item.match(re);
                // a = params[0] ? params[0] * a : a;
                // d = params[1] ? params[1] * d : params[0] * d;

                let [i, j = i] = params.map(a => parseFloat(a));
                matrixScale = $M([
                    [i, 0, 0],
                    [0, j, 0],
                    [0, 0, 1]
                ]);

                matrixResult = matrixResult ? matrixResult.x(matrixScale) : matrixDefault.x(matrixScale);
                // if(matrixResult) {
                //     matrixResult.elements[0][0] = i * matrixResult.elements[0][0];
                //     matrixResult.elements[1][1] = i * matrixResult.elements[1][1];
                // }else {
                //     matrixDefault.elements[0][0] = i * matrixDefault.elements[0][0];
                //     matrixDefault.elements[1][1] = i * matrixDefault.elements[1][1];
                // }
            }
            if (item.includes('translate')) {
                let params = item.match(re);
                let [x = 0, y = 0] = params.map(a => parseFloat(a));
                e = x;
                f = y;

                matrixTranslate = $M([
                    [1, 0, x],
                    [0, 1, y],
                    [0, 0, 1]
                ]);

                if (matrixResult) {
                    matrixResult.elements[0][2] = x;
                    matrixResult.elements[1][2] = y;
                } else {
                    matrixDefault.elements[0][2] = x;
                    matrixDefault.elements[1][2] = y;
                }
                // matrixResult = matrixResult ? matrixResult.x(matrixTranslate): matrixDefault.x(matrixTranslate);
            }
        })

        const [[a1, a2, a3], [b1, b2, b3], [c1, c2, c3]] = matrixResult.elements;

        return `matrix(${a1}, ${b1}, ${a2}, ${b2}, ${a3}, ${b3})`;


    } catch (error) {
        console.log(error)
    }
}
stringToMatrix(transformString) // matrix(1.5, 0.7499999999999999, -0.401923788646684, 1.299038105676658, 30, 0)

参考连接:

1、https://zh.wikipedia.org/wiki/%E7%9F%A9%E9%98%B5#%E6%A0%87%E8%AE%B0

2、http://www.ruanyifeng.com/blog/2015/09/matrix-multiplication.html

3、https://www.jianshu.com/p/dcf189998ae2

到此这篇关于深入理解CSS 中 transform matrix矩阵变换的文章就介绍到这了,更多相关CSS 中 transform matrix矩阵变换内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章,希望大家以后多多支持三水点靠木!

 
HTML / CSS 相关文章推荐
修复iPhone的safari浏览器上submit按钮圆角bug
Dec 24 HTML / CSS
使用CSS3的font-face字体嵌入样式的方法讲解
May 13 HTML / CSS
CSS3中的@keyframes关键帧动画的选择器绑定
Jun 13 HTML / CSS
CSS3效果:自定义“W”形运行轨迹实例
Mar 29 HTML / CSS
html5中localStorage本地存储的简单使用
Jun 16 HTML / CSS
html5跨域通讯之postMessage的用法总结
Nov 07 HTML / CSS
详解移动端html5页面长按实现高亮全选文本内容的兼容解决方案
Dec 03 HTML / CSS
HTML5实现自带进度条和滑块滑杆效果
Apr 17 HTML / CSS
使用html2canvas实现将html内容写入到canvas中生成图片
Jan 03 HTML / CSS
详解淘宝H5 sign加密算法
Aug 25 HTML / CSS
深入理解CSS 中 transform matrix矩阵变换问题
Aug 30 HTML / CSS
使用CSS定位HTML元素的实现方法
Jul 07 HTML / CSS
CSS+HTML 实现顶部导航栏功能
Aug 30 #HTML / CSS
HTML5 语义化标签(移动端必备)
Aug 23 #HTML / CSS
CSS3 Tab动画实例之背景切换动态效果
Aug 23 #HTML / CSS
CSS 使用 resize 实现图片拖拽切换预览功能(强大功能)
如何使用 resize 实现图片切换预览功能
Aug 23 #HTML / CSS
css中z-index: 0和z-index: auto的区别
Aug 23 #HTML / CSS
CSS Transition通过改变Height实现展开收起元素
Aug 07 #HTML / CSS
You might like
PHP生成带有雪花背景的验证码
2006/10/09 PHP
linux下为php添加curl扩展的方法
2011/07/29 PHP
PHP获取本周第一天和最后一天示例代码
2014/02/24 PHP
PHP查询附近的人及其距离的实现方法
2016/05/11 PHP
Yii实现复选框批量操作实例代码
2017/03/15 PHP
快速解决jQuery与其他库冲突的方法介绍
2014/01/02 Javascript
超棒的响应式布局jQuery插件Freetile.js
2014/11/17 Javascript
EasyUI中实现form表单提交的示例分享
2015/03/01 Javascript
JS弹出层遮罩,隐藏背景页面滚动条细节优化分析
2016/04/29 Javascript
简单实现jQuery进度条轮播实例代码
2016/06/20 Javascript
jQuery实现联动下拉列表查询框
2017/01/04 Javascript
JavaScript定时器制作弹窗小广告
2017/02/05 Javascript
jquery+css实现侧边导航栏效果
2017/06/12 jQuery
vue之数据交互实例代码
2017/06/16 Javascript
webpack学习笔记之代码分割和按需加载的实例详解
2017/07/20 Javascript
NodeJS多种创建WebSocket监听的方式(三种)
2020/06/04 NodeJs
解决vue props传Array/Object类型值,子组件报错的情况
2020/11/07 Javascript
原生JS实现音乐播放器的示例代码
2021/02/25 Javascript
python django事务transaction源码分析详解
2017/03/17 Python
Python自动发邮件脚本
2017/03/31 Python
Python基于辗转相除法求解最大公约数的方法示例
2018/04/04 Python
解决python3 HTMLTestRunner测试报告中文乱码的问题
2018/12/17 Python
python实现逐个读取txt字符并修改
2018/12/24 Python
Python OpenCV之图片缩放的实现(cv2.resize)
2019/06/28 Python
加拿大拼图大师:Puzzle Master
2020/12/28 全球购物
Overload和Override的区别
2012/09/02 面试题
院药学专业个人求职信
2013/09/21 职场文书
幼儿园安全责任书
2014/04/14 职场文书
关于护士节的演讲稿
2014/05/26 职场文书
法英专业大学生职业生涯规划范文:衡外情,量己力!
2014/09/23 职场文书
小区的门卫岗位职责
2014/10/01 职场文书
2014年人事工作总结范文
2014/11/19 职场文书
2015年幼儿园班主任个人工作总结
2015/10/22 职场文书
​(迎国庆)作文之我爱我的祖国
2019/09/19 职场文书
励志语录:只有自己足够强大,才能不被别人践踏
2020/01/09 职场文书
分享一些Java的常用工具
2021/06/11 Java/Android