UNPKG

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
'use strict'; 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