UNPKG

css-transform-matrix-plugin

Version:

A webpack plugin that automatically converts CSS transform properties to matrix3d for GPU acceleration

1,006 lines (919 loc) 28.4 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.CSSTransformMatrix = {})); })(this, (function (exports) { 'use strict'; function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var parse; var hasRequiredParse; function requireParse () { if (hasRequiredParse) return parse; hasRequiredParse = 1; var openParentheses = "(".charCodeAt(0); var closeParentheses = ")".charCodeAt(0); var singleQuote = "'".charCodeAt(0); var doubleQuote = '"'.charCodeAt(0); var backslash = "\\".charCodeAt(0); var slash = "/".charCodeAt(0); var comma = ",".charCodeAt(0); var colon = ":".charCodeAt(0); var star = "*".charCodeAt(0); var uLower = "u".charCodeAt(0); var uUpper = "U".charCodeAt(0); var plus = "+".charCodeAt(0); var isUnicodeRange = /^[a-f0-9?-]+$/i; parse = function(input) { var tokens = []; var value = input; var next, quote, prev, token, escape, escapePos, whitespacePos, parenthesesOpenPos; var pos = 0; var code = value.charCodeAt(pos); var max = value.length; var stack = [{ nodes: tokens }]; var balanced = 0; var parent; var name = ""; var before = ""; var after = ""; while (pos < max) { // Whitespaces if (code <= 32) { next = pos; do { next += 1; code = value.charCodeAt(next); } while (code <= 32); token = value.slice(pos, next); prev = tokens[tokens.length - 1]; if (code === closeParentheses && balanced) { after = token; } else if (prev && prev.type === "div") { prev.after = token; prev.sourceEndIndex += token.length; } else if ( code === comma || code === colon || (code === slash && value.charCodeAt(next + 1) !== star && (!parent || (parent && parent.type === "function" && parent.value !== "calc"))) ) { before = token; } else { tokens.push({ type: "space", sourceIndex: pos, sourceEndIndex: next, value: token }); } pos = next; // Quotes } else if (code === singleQuote || code === doubleQuote) { next = pos; quote = code === singleQuote ? "'" : '"'; token = { type: "string", sourceIndex: pos, quote: quote }; do { escape = false; next = value.indexOf(quote, next + 1); if (~next) { escapePos = next; while (value.charCodeAt(escapePos - 1) === backslash) { escapePos -= 1; escape = !escape; } } else { value += quote; next = value.length - 1; token.unclosed = true; } } while (escape); token.value = value.slice(pos + 1, next); token.sourceEndIndex = token.unclosed ? next : next + 1; tokens.push(token); pos = next + 1; code = value.charCodeAt(pos); // Comments } else if (code === slash && value.charCodeAt(pos + 1) === star) { next = value.indexOf("*/", pos); token = { type: "comment", sourceIndex: pos, sourceEndIndex: next + 2 }; if (next === -1) { token.unclosed = true; next = value.length; token.sourceEndIndex = next; } token.value = value.slice(pos + 2, next); tokens.push(token); pos = next + 2; code = value.charCodeAt(pos); // Operation within calc } else if ( (code === slash || code === star) && parent && parent.type === "function" && parent.value === "calc" ) { token = value[pos]; tokens.push({ type: "word", sourceIndex: pos - before.length, sourceEndIndex: pos + token.length, value: token }); pos += 1; code = value.charCodeAt(pos); // Dividers } else if (code === slash || code === comma || code === colon) { token = value[pos]; tokens.push({ type: "div", sourceIndex: pos - before.length, sourceEndIndex: pos + token.length, value: token, before: before, after: "" }); before = ""; pos += 1; code = value.charCodeAt(pos); // Open parentheses } else if (openParentheses === code) { // Whitespaces after open parentheses next = pos; do { next += 1; code = value.charCodeAt(next); } while (code <= 32); parenthesesOpenPos = pos; token = { type: "function", sourceIndex: pos - name.length, value: name, before: value.slice(parenthesesOpenPos + 1, next) }; pos = next; if (name === "url" && code !== singleQuote && code !== doubleQuote) { next -= 1; do { escape = false; next = value.indexOf(")", next + 1); if (~next) { escapePos = next; while (value.charCodeAt(escapePos - 1) === backslash) { escapePos -= 1; escape = !escape; } } else { value += ")"; next = value.length - 1; token.unclosed = true; } } while (escape); // Whitespaces before closed whitespacePos = next; do { whitespacePos -= 1; code = value.charCodeAt(whitespacePos); } while (code <= 32); if (parenthesesOpenPos < whitespacePos) { if (pos !== whitespacePos + 1) { token.nodes = [ { type: "word", sourceIndex: pos, sourceEndIndex: whitespacePos + 1, value: value.slice(pos, whitespacePos + 1) } ]; } else { token.nodes = []; } if (token.unclosed && whitespacePos + 1 !== next) { token.after = ""; token.nodes.push({ type: "space", sourceIndex: whitespacePos + 1, sourceEndIndex: next, value: value.slice(whitespacePos + 1, next) }); } else { token.after = value.slice(whitespacePos + 1, next); token.sourceEndIndex = next; } } else { token.after = ""; token.nodes = []; } pos = next + 1; token.sourceEndIndex = token.unclosed ? next : pos; code = value.charCodeAt(pos); tokens.push(token); } else { balanced += 1; token.after = ""; token.sourceEndIndex = pos + 1; tokens.push(token); stack.push(token); tokens = token.nodes = []; parent = token; } name = ""; // Close parentheses } else if (closeParentheses === code && balanced) { pos += 1; code = value.charCodeAt(pos); parent.after = after; parent.sourceEndIndex += after.length; after = ""; balanced -= 1; stack[stack.length - 1].sourceEndIndex = pos; stack.pop(); parent = stack[balanced]; tokens = parent.nodes; // Words } else { next = pos; do { if (code === backslash) { next += 1; } next += 1; code = value.charCodeAt(next); } while ( next < max && !( code <= 32 || code === singleQuote || code === doubleQuote || code === comma || code === colon || code === slash || code === openParentheses || (code === star && parent && parent.type === "function" && parent.value === "calc") || (code === slash && parent.type === "function" && parent.value === "calc") || (code === closeParentheses && balanced) ) ); token = value.slice(pos, next); if (openParentheses === code) { name = token; } else if ( (uLower === token.charCodeAt(0) || uUpper === token.charCodeAt(0)) && plus === token.charCodeAt(1) && isUnicodeRange.test(token.slice(2)) ) { tokens.push({ type: "unicode-range", sourceIndex: pos, sourceEndIndex: next, value: token }); } else { tokens.push({ type: "word", sourceIndex: pos, sourceEndIndex: next, value: token }); } pos = next; } } for (pos = stack.length - 1; pos; pos -= 1) { stack[pos].unclosed = true; stack[pos].sourceEndIndex = value.length; } return stack[0].nodes; }; return parse; } var walk; var hasRequiredWalk; function requireWalk () { if (hasRequiredWalk) return walk; hasRequiredWalk = 1; walk = function walk(nodes, cb, bubble) { var i, max, node, result; for (i = 0, max = nodes.length; i < max; i += 1) { node = nodes[i]; if (!bubble) { result = cb(node, i, nodes); } if ( result !== false && node.type === "function" && Array.isArray(node.nodes) ) { walk(node.nodes, cb, bubble); } if (bubble) { cb(node, i, nodes); } } }; return walk; } var stringify_1; var hasRequiredStringify; function requireStringify () { if (hasRequiredStringify) return stringify_1; hasRequiredStringify = 1; function stringifyNode(node, custom) { var type = node.type; var value = node.value; var buf; var customResult; if (custom && (customResult = custom(node)) !== undefined) { return customResult; } else if (type === "word" || type === "space") { return value; } else if (type === "string") { buf = node.quote || ""; return buf + value + (node.unclosed ? "" : buf); } else if (type === "comment") { return "/*" + value + (node.unclosed ? "" : "*/"); } else if (type === "div") { return (node.before || "") + value + (node.after || ""); } else if (Array.isArray(node.nodes)) { buf = stringify(node.nodes, custom); if (type !== "function") { return buf; } return ( value + "(" + (node.before || "") + buf + (node.after || "") + (node.unclosed ? "" : ")") ); } return value; } function stringify(nodes, custom) { var result, i; if (Array.isArray(nodes)) { result = ""; for (i = nodes.length - 1; ~i; i -= 1) { result = stringifyNode(nodes[i], custom) + result; } return result; } return stringifyNode(nodes, custom); } stringify_1 = stringify; return stringify_1; } var unit; var hasRequiredUnit; function requireUnit () { if (hasRequiredUnit) return unit; hasRequiredUnit = 1; var minus = "-".charCodeAt(0); var plus = "+".charCodeAt(0); var dot = ".".charCodeAt(0); var exp = "e".charCodeAt(0); var EXP = "E".charCodeAt(0); // Check if three code points would start a number // https://www.w3.org/TR/css-syntax-3/#starts-with-a-number function likeNumber(value) { var code = value.charCodeAt(0); var nextCode; if (code === plus || code === minus) { nextCode = value.charCodeAt(1); if (nextCode >= 48 && nextCode <= 57) { return true; } var nextNextCode = value.charCodeAt(2); if (nextCode === dot && nextNextCode >= 48 && nextNextCode <= 57) { return true; } return false; } if (code === dot) { nextCode = value.charCodeAt(1); if (nextCode >= 48 && nextCode <= 57) { return true; } return false; } if (code >= 48 && code <= 57) { return true; } return false; } // Consume a number // https://www.w3.org/TR/css-syntax-3/#consume-number unit = function(value) { var pos = 0; var length = value.length; var code; var nextCode; var nextNextCode; if (length === 0 || !likeNumber(value)) { return false; } code = value.charCodeAt(pos); if (code === plus || code === minus) { pos++; } while (pos < length) { code = value.charCodeAt(pos); if (code < 48 || code > 57) { break; } pos += 1; } code = value.charCodeAt(pos); nextCode = value.charCodeAt(pos + 1); if (code === dot && nextCode >= 48 && nextCode <= 57) { pos += 2; while (pos < length) { code = value.charCodeAt(pos); if (code < 48 || code > 57) { break; } pos += 1; } } code = value.charCodeAt(pos); nextCode = value.charCodeAt(pos + 1); nextNextCode = value.charCodeAt(pos + 2); if ( (code === exp || code === EXP) && ((nextCode >= 48 && nextCode <= 57) || ((nextCode === plus || nextCode === minus) && nextNextCode >= 48 && nextNextCode <= 57)) ) { pos += nextCode === plus || nextCode === minus ? 3 : 2; while (pos < length) { code = value.charCodeAt(pos); if (code < 48 || code > 57) { break; } pos += 1; } } return { number: value.slice(0, pos), unit: value.slice(pos) }; }; return unit; } var lib; var hasRequiredLib; function requireLib () { if (hasRequiredLib) return lib; hasRequiredLib = 1; var parse = requireParse(); var walk = requireWalk(); var stringify = requireStringify(); function ValueParser(value) { if (this instanceof ValueParser) { this.nodes = parse(value); return this; } return new ValueParser(value); } ValueParser.prototype.toString = function() { return Array.isArray(this.nodes) ? stringify(this.nodes) : ""; }; ValueParser.prototype.walk = function(cb, bubble) { walk(this.nodes, cb, bubble); return this; }; ValueParser.unit = requireUnit(); ValueParser.walk = walk; ValueParser.stringify = stringify; lib = ValueParser; return lib; } var libExports = requireLib(); var valueParser = /*@__PURE__*/getDefaultExportFromCjs(libExports); class TransformParser { // 解析 transform 属性值 static parseTransformValue(value) { const parsed = valueParser(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, }; } } /** * 运行时转换单个 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, }; } exports.batchTransform = batchTransform; exports.transformCSSString = transformCSSString; exports.transformStyleObject = transformStyleObject; exports.transformValue = transformValue; })); //# sourceMappingURL=runtime.js.map