css-transform-matrix-plugin
Version:
A webpack plugin that automatically converts CSS transform properties to matrix3d for GPU acceleration
585 lines (573 loc) • 19.7 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
var postcss = require('postcss');
var valueParser = require('postcss-value-parser');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var postcss__default = /*#__PURE__*/_interopDefault(postcss);
var valueParser__default = /*#__PURE__*/_interopDefault(valueParser);
class TransformParser {
// 解析 transform 属性值
static parseTransformValue(value) {
const parsed = valueParser__default.default(value);
const functions = [];
parsed.walk((node) => {
if (node.type === 'function') {
const functionNode = node;
const args = this.extractFunctionArgs(functionNode);
functions.push({
name: functionNode.value,
args,
});
}
});
return functions;
}
// 提取函数参数并转换为数值
static extractFunctionArgs(node) {
const args = [];
if (node.nodes) {
node.nodes.forEach((arg) => {
if (arg.type === 'word') {
const numValue = this.parseNumericValue(arg.value);
if (numValue !== null) {
args.push(numValue);
}
}
});
}
return args;
}
// 解析数值(支持 px, deg, % 等单位)
static parseNumericValue(value) {
// 移除单位,只保留数值
const match = value.match(/^(-?\d*\.?\d+)(px|deg|%|em|rem)?$/);
if (match) {
const num = parseFloat(match[1]);
const unit = match[2];
// 角度转弧度
if (unit === 'deg') {
return (num * Math.PI) / 180;
}
return num;
}
return null;
}
}
// 角度转换函数
// 创建单位矩阵
function createIdentityMatrix() {
return {
m11: 1,
m12: 0,
m13: 0,
m14: 0,
m21: 0,
m22: 1,
m23: 0,
m24: 0,
m31: 0,
m32: 0,
m33: 1,
m34: 0,
m41: 0,
m42: 0,
m43: 0,
m44: 1,
};
}
// 矩阵乘法 - 将两个 4x4 矩阵相乘
function multiplyMatrices(a, b) {
return {
// 第一行
m11: a.m11 * b.m11 + a.m12 * b.m21 + a.m13 * b.m31 + a.m14 * b.m41,
m12: a.m11 * b.m12 + a.m12 * b.m22 + a.m13 * b.m32 + a.m14 * b.m42,
m13: a.m11 * b.m13 + a.m12 * b.m23 + a.m13 * b.m33 + a.m14 * b.m43,
m14: a.m11 * b.m14 + a.m12 * b.m24 + a.m13 * b.m34 + a.m14 * b.m44,
// 第二行
m21: a.m21 * b.m11 + a.m22 * b.m21 + a.m23 * b.m31 + a.m24 * b.m41,
m22: a.m21 * b.m12 + a.m22 * b.m22 + a.m23 * b.m32 + a.m24 * b.m42,
m23: a.m21 * b.m13 + a.m22 * b.m23 + a.m23 * b.m33 + a.m24 * b.m43,
m24: a.m21 * b.m14 + a.m22 * b.m24 + a.m23 * b.m34 + a.m24 * b.m44,
// 第三行
m31: a.m31 * b.m11 + a.m32 * b.m21 + a.m33 * b.m31 + a.m34 * b.m41,
m32: a.m31 * b.m12 + a.m32 * b.m22 + a.m33 * b.m32 + a.m34 * b.m42,
m33: a.m31 * b.m13 + a.m32 * b.m23 + a.m33 * b.m33 + a.m34 * b.m43,
m34: a.m31 * b.m14 + a.m32 * b.m24 + a.m33 * b.m34 + a.m34 * b.m44,
// 第四行
m41: a.m41 * b.m11 + a.m42 * b.m21 + a.m43 * b.m31 + a.m44 * b.m41,
m42: a.m41 * b.m12 + a.m42 * b.m22 + a.m43 * b.m32 + a.m44 * b.m42,
m43: a.m41 * b.m13 + a.m42 * b.m23 + a.m43 * b.m33 + a.m44 * b.m43,
m44: a.m41 * b.m14 + a.m42 * b.m24 + a.m43 * b.m34 + a.m44 * b.m44,
};
}
// CSS Transform 矩阵转换器
class MatrixTransformer {
// 将transform函数数组抓暖胃3D矩阵
static transformsToMatrix3D(functions) {
let result = createIdentityMatrix();
for (const func of functions) {
const matrix = this.createMatrixFromFunction(func);
result = multiplyMatrices(result, matrix);
}
return result;
}
// 根据单个transform函数创建矩阵
static createMatrixFromFunction(func) {
const { name, args } = func;
switch (name) {
case 'translateX':
return this.createTranslateXMatrix(args[0] || 0);
case 'translateY':
return this.createTranslateYMatrix(args[0] || 0);
case 'translateZ':
return this.createTranslateZMatrix(args[0] || 0);
case 'translate':
return this.createTranslateMatrix(args[0] || 0, args[1] || 0);
case 'translate3d':
return this.createTranslate3DMatrix(args[0] || 0, args[1] || 0, args[2] || 0);
case 'scaleX':
return this.createScaleXMatrix(args[0] || 1);
case 'scaleY':
return this.createScaleYMatrix(args[0] || 1);
case 'scaleZ':
return this.createScaleZMatrix(args[0] || 1);
case 'scale':
return this.createScaleMatrix(args[0] || 1, args[1] || args[0] || 1);
case 'scale3d':
return this.createScale3DMatrix(args[0] || 1, args[1] || 1, args[2] || 1);
case 'rotate':
case 'rotateZ':
return this.createRotateZMatrix(args[0] || 0);
case 'rotateX':
return this.createRotateXMatrix(args[0] || 0);
case 'rotateY':
return this.createRotateYMatrix(args[0] || 0);
case 'skewX':
return this.createSkewXMatrix(args[0] || 0);
case 'skewY':
return this.createSkewYMatrix(args[0] || 0);
case 'skew':
return this.createSkewMatrix(args[0] || 0, args[1] || 0);
case 'matrix3d':
if (args.length >= 16) {
return this.createMatrix3DFromArray(args);
}
else {
console.warn(`matrix3d requires 16 arguments, got ${args.length}`);
return createIdentityMatrix();
}
case 'matrix':
if (args.length >= 6) {
return this.createMatrixFrom2D(args);
}
else {
console.warn(`matrix requires 6 arguments, got ${args.length}`);
return createIdentityMatrix();
}
default:
console.warn(`Unsupported transform function: ${name}`);
return createIdentityMatrix();
}
}
// 位移矩阵
static createTranslateXMatrix(x) {
const matrix = createIdentityMatrix();
matrix.m41 = x;
return matrix;
}
static createTranslateYMatrix(y) {
const matrix = createIdentityMatrix();
matrix.m42 = y;
return matrix;
}
static createTranslateZMatrix(z) {
const matrix = createIdentityMatrix();
matrix.m43 = z;
return matrix;
}
static createTranslateMatrix(x, y) {
const matrix = createIdentityMatrix();
matrix.m41 = x;
matrix.m42 = y;
return matrix;
}
static createTranslate3DMatrix(x, y, z) {
const matrix = createIdentityMatrix();
matrix.m41 = x;
matrix.m42 = y;
matrix.m43 = z;
return matrix;
}
// 缩放矩阵
static createScaleXMatrix(sx) {
const matrix = createIdentityMatrix();
matrix.m11 = sx;
return matrix;
}
static createScaleYMatrix(sy) {
const matrix = createIdentityMatrix();
matrix.m22 = sy;
return matrix;
}
static createScaleZMatrix(sz) {
const matrix = createIdentityMatrix();
matrix.m33 = sz;
return matrix;
}
static createScaleMatrix(sx, sy) {
const matrix = createIdentityMatrix();
matrix.m11 = sx;
matrix.m22 = sy;
return matrix;
}
static createScale3DMatrix(sx, sy, sz) {
const matrix = createIdentityMatrix();
matrix.m11 = sx;
matrix.m22 = sy;
matrix.m33 = sz;
return matrix;
}
// 旋转矩阵
static createRotateXMatrix(angle) {
const matrix = createIdentityMatrix();
const cos = Math.cos(angle);
const sin = Math.sin(angle);
matrix.m22 = cos;
matrix.m23 = -sin;
matrix.m32 = sin;
matrix.m33 = cos;
return matrix;
}
static createRotateYMatrix(angle) {
const matrix = createIdentityMatrix();
const cos = Math.cos(angle);
const sin = Math.sin(angle);
matrix.m11 = cos;
matrix.m13 = sin;
matrix.m31 = -sin;
matrix.m33 = cos;
return matrix;
}
static createRotateZMatrix(angle) {
const matrix = createIdentityMatrix();
const cos = Math.cos(angle);
const sin = Math.sin(angle);
matrix.m11 = cos;
matrix.m12 = -sin;
matrix.m21 = sin;
matrix.m22 = cos;
return matrix;
}
// 倾斜矩阵
static createSkewXMatrix(angle) {
const matrix = createIdentityMatrix();
matrix.m21 = Math.tan(angle);
return matrix;
}
static createSkewYMatrix(angle) {
const matrix = createIdentityMatrix();
matrix.m12 = Math.tan(angle);
return matrix;
}
static createSkewMatrix(angleX, angleY) {
const matrix = createIdentityMatrix();
matrix.m12 = Math.tan(angleY);
matrix.m21 = Math.tan(angleX);
return matrix;
}
/**
* 将矩阵转换为 CSS matrix3d 字符串
*/
static matrixToCSS(matrix) {
const values = [
matrix.m11,
matrix.m12,
matrix.m13,
matrix.m14,
matrix.m21,
matrix.m22,
matrix.m23,
matrix.m24,
matrix.m31,
matrix.m32,
matrix.m33,
matrix.m34,
matrix.m41,
matrix.m42,
matrix.m43,
matrix.m44,
];
// 保留6位小数,移除尾随零
const formattedValues = values.map((v) => parseFloat(v.toFixed(6)).toString());
return `matrix3d(${formattedValues.join(', ')})`;
}
// 从 matrix3d 的 16 个参数创建矩阵
static createMatrix3DFromArray(args) {
return {
m11: args[0],
m12: args[1],
m13: args[2],
m14: args[3],
m21: args[4],
m22: args[5],
m23: args[6],
m24: args[7],
m31: args[8],
m32: args[9],
m33: args[10],
m34: args[11],
m41: args[12],
m42: args[13],
m43: args[14],
m44: args[15],
};
}
// 从 2D matrix 的 6 个参数创建 3D 矩阵
static createMatrixFrom2D(args) {
return {
m11: args[0],
m12: args[1],
m13: 0,
m14: 0,
m21: args[2],
m22: args[3],
m23: 0,
m24: 0,
m31: 0,
m32: 0,
m33: 1,
m34: 0,
m41: args[4],
m42: args[5],
m43: 0,
m44: 1,
};
}
}
function createPostCSSPlugin(options = {}) {
const config = {
enabled: true,
keepOriginal: false,
verbose: false,
...options,
};
return {
postcssPlugin: 'css-transform-matrix',
Declaration: (decl) => {
if (!config.enabled ||
decl.prop !== 'transform' ||
decl.value === 'none') {
return;
}
try {
const originalValue = decl.value;
// 跳过已经处理的 matrix3d
if (originalValue.includes('matrix3d(')) {
if (config.verbose) {
console.log(`[CSS Transform Matrix] Skipping: ${originalValue}`);
}
return;
}
const functions = TransformParser.parseTransformValue(originalValue);
if (functions.length === 0) {
return;
}
const matrix = MatrixTransformer.transformsToMatrix3D(functions);
const matrixCSS = MatrixTransformer.matrixToCSS(matrix);
if (config.keepOriginal) {
decl.cloneBefore({
prop: `/* original-transform */`,
value: originalValue,
});
}
decl.value = matrixCSS;
if (config.verbose) {
console.log(`[CSS Transform Matrix] ${originalValue} -> ${matrixCSS}`);
}
}
catch (error) {
if (config.verbose) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`[CSS Transform Matrix] Failed: ${decl.value}`, errorMessage);
}
}
},
};
}
// PostCSS 插件标识
createPostCSSPlugin.postcss = true;
/**
* 运行时转换单个 transform 值
*/
function transformValue(transformValue) {
try {
if (!transformValue ||
transformValue === 'none' ||
transformValue.includes('matrix3d(')) {
return transformValue;
}
const functions = TransformParser.parseTransformValue(transformValue);
if (functions.length === 0) {
return transformValue;
}
const matrix = MatrixTransformer.transformsToMatrix3D(functions);
return MatrixTransformer.matrixToCSS(matrix);
}
catch (error) {
console.warn('CSS Transform Matrix Runtime: Failed to transform', transformValue, error);
return transformValue; // 返回原值作为降级
}
}
/**
* 转换样式对象中的 transform 属性
*/
function transformStyleObject(styles) {
const result = { ...styles };
if (typeof result.transform === 'string') {
result.transform = transformValue(result.transform);
}
return result;
}
/**
* 转换 CSS 字符串中的所有 transform 属性
*/
function transformCSSString(css) {
const transformRegex = /transform\s*:\s*([^;]+);/g;
return css.replace(transformRegex, (match, transformValue) => {
const transformed = transformValue(transformValue.trim());
return `transform: ${transformed};`;
});
}
/**
* 批量转换多个 transform 值
*/
function batchTransform(transforms) {
return transforms.map(transformValue);
}
// 安全的浏览器环境检测
if (typeof globalThis !== 'undefined' &&
typeof globalThis.window !== 'undefined') {
globalThis.window.CSSTransformMatrix = {
transformValue,
transformStyleObject,
transformCSSString,
batchTransform,
};
}
class CSSTransformMatrixPlugin {
constructor(options = {}) {
this.options = {
enabled: true,
extensions: ['.css', '.scss', '.sass', '.less'],
keepOriginal: false,
verbose: false,
...options,
};
}
apply(compiler) {
if (!this.options.enabled) {
return;
}
const pluginName = 'CSSTransformMatrixPlugin';
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
compilation.hooks.processAssets.tapAsync({
name: pluginName,
stage: compilation.PROCESS_ASSETS_STAGE_OPTIMIZE,
}, async (assets, callback) => {
try {
await this.processAssets(assets, compilation);
callback();
}
catch (error) {
callback(error instanceof Error ? error : new Error(String(error)));
}
});
});
}
async processAssets(assets, compilation) {
const cssAssets = Object.keys(assets).filter((name) => name.endsWith('.css') ||
this.options.extensions.some((ext) => name.endsWith(ext)));
for (const assetName of cssAssets) {
try {
const asset = assets[assetName];
const source = asset.source();
if (this.options.verbose) {
console.log(`[CSS Transform Matrix] Processing: ${assetName}`);
}
const processed = await this.processCSS(source);
assets[assetName] = this.createNewAsset(processed);
if (this.options.verbose) {
console.log(`[CSS Transform Matrix] Transformed ${assetName}: ${source.length} -> ${processed.length} bytes`);
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
compilation.warnings.push(new Error(`CSS Transform Matrix Plugin: Error processing ${assetName}: ${errorMessage}`));
}
}
}
createNewAsset(content) {
return {
source: () => content,
size: () => content.length,
};
}
async processCSS(css) {
const processor = postcss__default.default([this.createTransformPlugin()]);
const result = await processor.process(css, { from: undefined });
return result.css;
}
createTransformPlugin() {
return {
postcssPlugin: 'css-transform-matrix',
Declaration: (decl) => {
if (decl.prop === 'transform' && decl.value !== 'none') {
this.processTransformDeclaration(decl);
}
},
};
}
processTransformDeclaration(decl) {
try {
const originalValue = decl.value;
if (originalValue.includes('matrix3d(')) {
if (this.options.verbose) {
console.log(`[CSS Transform Matrix] Skipping: ${originalValue}`);
}
return;
}
const functions = TransformParser.parseTransformValue(originalValue);
if (functions.length === 0) {
return;
}
const matrix = MatrixTransformer.transformsToMatrix3D(functions);
const matrixCSS = MatrixTransformer.matrixToCSS(matrix);
if (this.options.keepOriginal) {
decl.cloneBefore({
prop: `/* original-transform */`,
value: originalValue,
});
}
decl.value = matrixCSS;
if (this.options.verbose) {
console.log(`[CSS Transform Matrix] ${originalValue} -> ${matrixCSS}`);
}
}
catch (error) {
if (this.options.verbose) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`[CSS Transform Matrix] Failed: ${decl.value}`, errorMessage);
}
}
}
}
exports.CSSTransformMatrixPlugin = CSSTransformMatrixPlugin;
exports.MatrixTransformer = MatrixTransformer;
exports.TransformParser = TransformParser;
exports.batchTransform = batchTransform;
exports.createPostCSSPlugin = createPostCSSPlugin;
exports.default = CSSTransformMatrixPlugin;
exports.transformCSSString = transformCSSString;
exports.transformStyleObject = transformStyleObject;
exports.transformValue = transformValue;
//# sourceMappingURL=index.js.map
;