为什么在scale后的父元素上设置子元素的offset会出现异常,如何解决?-灵析社区

今天吃什么你说吧

我尝试在一个经过transform:scale()修改后的父元素“playground”上设置其子元素“ball”的offset属性,但是过程中出现了难以描述的异常,似乎每一次设置offset,其都会与offset所提供的数值有所偏差,必须经过多次修改,其才能正确地符合offset提供的位置。 我的代码如下: var new_scale = parseInt(一个输入值,小于3且大于0.3) $("#playground").css({ transform: 'scale(' + new_scale + ')', }); $("#ball").on("click",function(){ $("#ball").offset({ left: 300, top: 300 }) }) 我猜想,或许可能与offset不兼容scale,或者scale并没有直接修改元素的位置有关,但是我的代码非常简单,我就只是希望ball能够到达这么一个位置,事实上他的确能移动,但是需要多次点击才行,我毫无头绪,希望能够得到任何建议,谢谢你们!

阅读量:11

点赞量:0

问AI
首先,安装一个 Matrix 库,因为自己写的话,一是麻烦易错,二是性能不佳: npm install gl-mat3 封装一个仿射矩阵类 const mat3 = require("gl-mat3"); class AffineMatrix { #values = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]); /** * @param {[number, number, number, number, number, number]} values */ constructor(values) { for (const i in values) { this.#values[i] = values[i]; } } /** * @methd getValues 读取矩阵的值 * @returns {Float32Array} */ getValues() { return new Float32Array(this.#values); } /** * @method rotate 旋转 * @param {number|string} [rad] 旋转的角度,如果是数字,默认单位是弧度(rad) * @returns {AffineMatrix} 返回一个全新矩阵,即不在原矩阵上修改 */ // rotate(rad = 0) { // let radius = rad; // if (typeof rad === "string") { // if (/rad$/.test(rad)) { // radius = parseFloat(rad.replace(/rad$/, "")); // } else if (/deg/.test(rad)) { // radius = parseFloat(rad.replace(/deg$/, "") / Math.PI); // } else { // radius = parseFloat(rad); // } // } // return new AffineMatrix( // mat3.rotate(new Float32Array(9), this.#values), // radius // ); // } /** * @method scale - 缩放 * @param {number} scaleX * @param {number} scaleY * @returns {AffineMatrix} * * @overload * @param {*} scaleX * @returns {AffineMatrix} */ scale(scaleX, scaleY) { if (scaleY === undefined) { return new AffineMatrix( mat3.scale(new Float32Array(9), this.#values, [scaleX, scaleX, 1]) ); } else { return new AffineMatrix( mat3.scale(new Float32Array(9), this.#values, [scaleX, scaleY, 1]) ); } } /** * @method multi 乘以一个向量或另外的矩阵 * @param {AffineMatrix} obj * @returns {AffineMatrix} * * @overload * @param {ArrayLike} obj * @returns {Float32Array} */ multi(obj) { if (obj instanceof AffineMatrix) { return new AffineMatrix( mat3.multiply(new Float32Array(9), this.#values, obj.getValues()) ); } else { const v = this.#values; const [x, y, z] = obj; return new Float32Array([ v[0] * x + v[1] * y + v[2] * z, v[3] * x + v[4] * y + v[5] * z, v[6] * x + v[7] * y + v[8] * z, ]); } } /** * @method invert 求逆矩阵 */ invert() { return new AffineMatrix(mat3.invert(new Float32Array(9), this.#values)); } toCSSMatrix() { const v = this.#values; return `matrix(${v[0]},${v[1]},${v[2]},${v[3]},${v[4]},${v[5]})`; } /** * @method fromCSSMatrix toCSSMatrix 的逆运算 * @param {string} matrixString */ static fromCSSMatrix(matrixString) { return new AffineMatrix([ ...matrixString .replace(/^matrix\(/, "") .split(",") .map((i) => parseFloat(i)), 0, 0, 1, ]); } } "jQuery" 上新增一个对应的方法: $.fn.transform = function(affine){ this.style.transform = affine.toCSSMatrix(); return $(this); } 给父元素设置"transform"的时候,一律不准使用 "$.fn.css",转而使用上述方法: const fnCss = $.fn.css; const $background = $("#background"); $.fn.css = function(...args){ const [val] = args; if($background.eq(this) && (val === "transfrom" || "transform" in val)){ throw new Error("禁止对 #playground 使用 .css('transfrom') ,应使用 transform()") } return $.fn.css.call(this, ...args); } // 在所有变换开始前,获取父元素位置和变换中心位置坐标,后面回来计算很麻烦 const {top, left, width, height} = $background[0].getBoundingClientRect(); const pOrgin = [top + width / 2, left + height / 2]; $background .transform( (new AffineMatrix([1, 0, 0, 0, 1, 0, 0, 0, 1])) .scale(new_scale) ) 子元素设置 "offset" 的时候究竟遇到什么问题,题目描述得不是很清楚,因此这里假设是期望子元素的位置免受"scale"影响。 $("#ball").click(function(){ const $this = $(this); const offsetX = 300; const offsetY = 300; // 计算目标 offset 在变换前的坐标 const originOffset = [offsetX + left - pOrgin[0], offsetY + top - pOrgin[1], 1]; // 计算目标 offset 在变换后的坐标 const scaledOffset = AffineMatrix .fromCSSMatrix($background[0].style.transform) .multi(originOffset); // 计算该坐标点变换前后的移动路径(向量) const offsetVector = [ scaledOffset[0] - originOffset[0], scaledOffset[1] - originOffset[1] ] // 沿着向量反向移动,就可以“回到”变化前应有的位置 $this.offset({ offsetX - offsetVector[0], offsetY - offsetVector[1] }) }); 写到这里发现想复杂了,这个方法可以扩展到任意 "transform" 2D 变换,实际上如果只考虑 "scale" 的话所有的计算可以进一步简化,计算难度降低到小学高年级数学水平。 因为真正的“干货”其实只在于计算那个"offsetVector",它的计算公式不过是把矩阵运算展开为线性方程组,很简单: $$ vx = (x + left - (left + width \div 2)) \times scale - (x + left - (left + width \div 2))\\ vy = (y + top - (top + height \div 2)) \times scale - (x + top - (top + height \div 2)) $$ 然后让变换后的点“移动”回去就行了: $$ x' = (x + left - (left + width \div 2)) \times scale - vx \\ y' = (y + top - (top + height \div 2)) \times scale - vy $$