有没有办法在应用 css 变换矩阵之前计算元素的结束位置?
Is there a way calculate the ending position of an element before css transform matrix is applied?
我需要在应用 css transform: matrix()
转换之前确定元素的结束边界矩形。我真的不知道从哪里开始,也找不到解决这个问题的好文章。
所以假设你有元素的原始位置 getBoundingClientRect()
如果你有应该应用的矩阵,是否有可靠的方法找到结束位置。
我构建了一个滚动控制器,我正在尝试通过屏幕映射元素进度,但我需要元素的开始和结束位置。因此,在应用 css 转换后,我需要元素的位置来确定元素何时离开屏幕。现在我只是应用矩阵,然后再次 运行ning getBoundingClientRect()
。但这似乎有点老套。
假设您有原始矩阵和结束矩阵。然后你在元素上 运行 getBoundingClientRect()
找到它的位置。有没有数学方法来计算新的边界矩形?
所以对于这个例子,我们将只使用一个 6 值矩阵。但也可以将其应用于完整的 16 值矩阵:
const boundingRect = element.getBoundingClientRect();
const startingMatrix = [1, 0, 0, 1, 0, 0];
const endingMatrix = [2, 1, -1, 2, 200, 400];
// Now calculate the new bounding rect of element after ending matrix is applied.
我根据下面评论推荐的这篇文章https://dev.opera.com/articles/understanding-the-css-transforms-matrix/尝试了以下方法。有些东西我显然遗漏了或不明白。这是我的尝试:
const applyToPoint = (matrix, point) => {
const multiplied = [
matrix[0] * point[0],
matrix[1] * point[1],
matrix[2] * point[0],
matrix[3] * point[1],
matrix[4] * point[0],
matrix[5] * point[1]
]
const result = [
multiplied[0] + multiplied[2] + multiplied[4],
multiplied[1] + multiplied[3] + multiplied[5]
]
return result
}
const box = document.querySelector('.box')
const startingMatrix = [1, 0, 0, 1, 0, 0]
box.style.transform = `matrix(1,0,0,1,0,0)`
const startingRect = box.getBoundingClientRect()
const endingMatrix = [2, 1, -1, 2, 200, 400]
box.style.transform = `matrix(2,1,-1,2,200,400)`
const endingRect = box.getBoundingClientRect()
const newPoint = applyToPoint(endingMatrix, [startingRect.x, startingRect.y])
console.log(newPoint, endingRect)
.box {
height: 50px;
width: 50px;
background: green;
margin: 50px 0;
}
<div class="box"></div>
我也试过了:
const applyToPoint = (matrix, point) => [
matrix[0] * point[0] + matrix[2] * point[1] + matrix[4],
matrix[1] * point[0] + matrix[3] * point[1] + matrix[5]
]
如有任何帮助,我们将不胜感激。或者指出正确的方向。
...to figure out when the element has left the screen.
如果您想在元素进入或退出视图时执行某些操作,那么 Intersection Observer 是更好的选择。
以整页模式查看以下代码段。
let observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting)
log.textContent = entry.target.textContent;
})
});
// set elements to observe
observer.observe(one);
observer.observe(two);
down.addEventListener('click', () => two.scrollIntoView({
behavior: "smooth",
block: "end",
inline: "nearest"
}));
body {
position: relative;
height: calc(150vh + 25px);
}
p {
position: sticky;
top: 1rem;
}
p #log {
background-color: yellow;
}
#down {
position: fixed;
top: 2rem;
right: 2rem;
background-color: skyblue;
padding: .3rem;
border-radius: 50%;
cursor: pointer;
user-select: none;
}
div {
height: 50px;
width: 50px;
background-color: wheat;
position: absolute;
left: 50vw;
}
#one {
background-color: rgb(250, 165, 165);
top: 50vh;
transform: translate(-50%, -50%);
}
#two {
background-color: lime;
top: 100%;
transform: translateX(-50%);
}
<span id="down"></span>
<p>Element in view: <span id="log"></span></p>
<div id="one">One</div>
<div id="two">Two</div>
API 是可配置的,您甚至可以看到有多少元素可见。
问题是您混合了两个不同的坐标系:
- 客户端系统(被
getBoundingClientRect()
使用)
- 本地系统(被
transform
使用)
如 linked article 中所述,
本地系统的原点是元素的中心:
When a transform is applied to an object, it creates a local coordinate system. By default, the origin —
the (0,0) point — of the local coordinate system lies at the object's center
让我们调用 [xc, yc]
元素的中心。
要转换一个点(在客户端系统中表示),您需要执行以下操作:
- 将坐标从客户端转换为本地系统(通过减去
xc
和 yc
)
- 应用变换矩阵
- 将生成的坐标从本地转换回客户端(通过添加
xc
和 yc
)
这是一个转换 4 个框角并计算相关边界框的代码:
var matrix = [2, 1, -1, 2, 200, 400];
var box = document.querySelector('.box');
var startingRect = box.getBoundingClientRect();
var xc = startingRect.left + startingRect.width/2;
var yc = startingRect.top + startingRect.height/2;
const applyMatrix = (matrix, point) => [
matrix[0] * point[0] + matrix[2] * point[1] + matrix[4],
matrix[1] * point[0] + matrix[3] * point[1] + matrix[5]
];
const clientToLocal = (point) => [point[0] - xc, point[1] - yc];
const localToClient = (point) => [point[0] + xc, point[1] + yc];
const transformPoint = (point) => localToClient(applyMatrix(matrix, clientToLocal(point)));
function getBoundingBox(pt1, pt2, pt3, pt4)
{
var x1 = Math.min(pt1[0], pt2[0], pt3[0], pt4[0]);
var y1 = Math.min(pt1[1], pt2[1], pt3[1], pt4[1]);
var x2 = Math.max(pt1[0], pt2[0], pt3[0], pt4[0]);
var y2 = Math.max(pt1[1], pt2[1], pt3[1], pt4[1]);
return {x: x1, y: y1, width: x2 - x1, height: y2 - y1};
}
var topLeft = [startingRect.left, startingRect.top];
var topRight = [startingRect.right, startingRect.top];
var bottomLeft = [startingRect.left, startingRect.bottom];
var bottomRight = [startingRect.right, startingRect.bottom];
var transformedBox = getBoundingBox(transformPoint(topLeft), transformPoint(topRight), transformPoint(bottomLeft), transformPoint(bottomRight));
console.log(transformedBox);
box.style.transform = 'matrix('+matrix.join()+')';
var endingRect = box.getBoundingClientRect();
console.log(endingRect);
输出:
{x: 158, y: 400, width: 150, height: 150}
DOMRect {x: 158, y: 400, width: 150, height: 150, top: 400, …}
如评论中所述,您必须简单地乘以矩阵(您可以只做 2 x 2 版本,然后添加平移变量。)但正如 Olivier 所述,您需要先平移坐标,然后将它们翻译回去。这很简单,只需计算中心即可。
我会把它写成一个函数,从一个矩形和矩阵(作为用于 CSS 转换的六个变量的平面数组)到另一个矩形,一个简单的 {left, right, top, bottom}
矩形或如果需要,一个 DomRect。
您可以在此代码段中看到它(如果使用“整页”展开它会更容易看到 link):
const transformRect = (rect, matrix) => {
const {left, top, right, bottom} = rect
const [a, b, c, d, tx, ty] = matrix
const dx = (left + right) / 2, dy = (top + bottom) / 2
const newCorners = [[left, top], [right, top], [right, bottom], [left, bottom]]
.map (([x, y]) => [
a * (x - dx) + c * (y - dy) + tx + dx,
b * (x - dx) + d * (y - dy) + ty + dy
])
const _left = Math .min (... newCorners .map (p => p [0]))
const _right = Math .max (... newCorners .map (p => p [0]))
const _top = Math .min (... newCorners .map (p => p [1]))
const _bottom = Math .max (... newCorners .map (p => p [1]))
return DOMRect.fromRect (
{x: _left, y: _top, width: _right - _left, height: _bottom - _top}
) // or just
// return {x: _left, y: _top, width: _right - _left, height: _bottom - _top}
}
const div = document .getElementById ('d2')
const rect = div.getBoundingClientRect()
console .log ('Before:' , rect)
const matrix = [2, 1, -1, 2, 200, 400]
div.style.transform = `matrix(${matrix .join (', ')})`
console .log ('After:', transformRect (rect, matrix))
.box {
height: 50px;
width: 50px;
background: green;
color: white;
}
#d1 {
background: #ccc;
position: absolute;
top: 8;
left: 8;
}
<div id="d1" class="box">shadow</div>
<div id="d2" class="box">content</div>
我需要在应用 css transform: matrix()
转换之前确定元素的结束边界矩形。我真的不知道从哪里开始,也找不到解决这个问题的好文章。
所以假设你有元素的原始位置 getBoundingClientRect()
如果你有应该应用的矩阵,是否有可靠的方法找到结束位置。
我构建了一个滚动控制器,我正在尝试通过屏幕映射元素进度,但我需要元素的开始和结束位置。因此,在应用 css 转换后,我需要元素的位置来确定元素何时离开屏幕。现在我只是应用矩阵,然后再次 运行ning getBoundingClientRect()
。但这似乎有点老套。
假设您有原始矩阵和结束矩阵。然后你在元素上 运行 getBoundingClientRect()
找到它的位置。有没有数学方法来计算新的边界矩形?
所以对于这个例子,我们将只使用一个 6 值矩阵。但也可以将其应用于完整的 16 值矩阵:
const boundingRect = element.getBoundingClientRect();
const startingMatrix = [1, 0, 0, 1, 0, 0];
const endingMatrix = [2, 1, -1, 2, 200, 400];
// Now calculate the new bounding rect of element after ending matrix is applied.
我根据下面评论推荐的这篇文章https://dev.opera.com/articles/understanding-the-css-transforms-matrix/尝试了以下方法。有些东西我显然遗漏了或不明白。这是我的尝试:
const applyToPoint = (matrix, point) => {
const multiplied = [
matrix[0] * point[0],
matrix[1] * point[1],
matrix[2] * point[0],
matrix[3] * point[1],
matrix[4] * point[0],
matrix[5] * point[1]
]
const result = [
multiplied[0] + multiplied[2] + multiplied[4],
multiplied[1] + multiplied[3] + multiplied[5]
]
return result
}
const box = document.querySelector('.box')
const startingMatrix = [1, 0, 0, 1, 0, 0]
box.style.transform = `matrix(1,0,0,1,0,0)`
const startingRect = box.getBoundingClientRect()
const endingMatrix = [2, 1, -1, 2, 200, 400]
box.style.transform = `matrix(2,1,-1,2,200,400)`
const endingRect = box.getBoundingClientRect()
const newPoint = applyToPoint(endingMatrix, [startingRect.x, startingRect.y])
console.log(newPoint, endingRect)
.box {
height: 50px;
width: 50px;
background: green;
margin: 50px 0;
}
<div class="box"></div>
我也试过了:
const applyToPoint = (matrix, point) => [
matrix[0] * point[0] + matrix[2] * point[1] + matrix[4],
matrix[1] * point[0] + matrix[3] * point[1] + matrix[5]
]
如有任何帮助,我们将不胜感激。或者指出正确的方向。
...to figure out when the element has left the screen.
如果您想在元素进入或退出视图时执行某些操作,那么 Intersection Observer 是更好的选择。
以整页模式查看以下代码段。
let observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting)
log.textContent = entry.target.textContent;
})
});
// set elements to observe
observer.observe(one);
observer.observe(two);
down.addEventListener('click', () => two.scrollIntoView({
behavior: "smooth",
block: "end",
inline: "nearest"
}));
body {
position: relative;
height: calc(150vh + 25px);
}
p {
position: sticky;
top: 1rem;
}
p #log {
background-color: yellow;
}
#down {
position: fixed;
top: 2rem;
right: 2rem;
background-color: skyblue;
padding: .3rem;
border-radius: 50%;
cursor: pointer;
user-select: none;
}
div {
height: 50px;
width: 50px;
background-color: wheat;
position: absolute;
left: 50vw;
}
#one {
background-color: rgb(250, 165, 165);
top: 50vh;
transform: translate(-50%, -50%);
}
#two {
background-color: lime;
top: 100%;
transform: translateX(-50%);
}
<span id="down"></span>
<p>Element in view: <span id="log"></span></p>
<div id="one">One</div>
<div id="two">Two</div>
API 是可配置的,您甚至可以看到有多少元素可见。
问题是您混合了两个不同的坐标系:
- 客户端系统(被
getBoundingClientRect()
使用) - 本地系统(被
transform
使用)
如 linked article 中所述, 本地系统的原点是元素的中心:
When a transform is applied to an object, it creates a local coordinate system. By default, the origin — the (0,0) point — of the local coordinate system lies at the object's center
让我们调用 [xc, yc]
元素的中心。
要转换一个点(在客户端系统中表示),您需要执行以下操作:
- 将坐标从客户端转换为本地系统(通过减去
xc
和yc
) - 应用变换矩阵
- 将生成的坐标从本地转换回客户端(通过添加
xc
和yc
)
这是一个转换 4 个框角并计算相关边界框的代码:
var matrix = [2, 1, -1, 2, 200, 400];
var box = document.querySelector('.box');
var startingRect = box.getBoundingClientRect();
var xc = startingRect.left + startingRect.width/2;
var yc = startingRect.top + startingRect.height/2;
const applyMatrix = (matrix, point) => [
matrix[0] * point[0] + matrix[2] * point[1] + matrix[4],
matrix[1] * point[0] + matrix[3] * point[1] + matrix[5]
];
const clientToLocal = (point) => [point[0] - xc, point[1] - yc];
const localToClient = (point) => [point[0] + xc, point[1] + yc];
const transformPoint = (point) => localToClient(applyMatrix(matrix, clientToLocal(point)));
function getBoundingBox(pt1, pt2, pt3, pt4)
{
var x1 = Math.min(pt1[0], pt2[0], pt3[0], pt4[0]);
var y1 = Math.min(pt1[1], pt2[1], pt3[1], pt4[1]);
var x2 = Math.max(pt1[0], pt2[0], pt3[0], pt4[0]);
var y2 = Math.max(pt1[1], pt2[1], pt3[1], pt4[1]);
return {x: x1, y: y1, width: x2 - x1, height: y2 - y1};
}
var topLeft = [startingRect.left, startingRect.top];
var topRight = [startingRect.right, startingRect.top];
var bottomLeft = [startingRect.left, startingRect.bottom];
var bottomRight = [startingRect.right, startingRect.bottom];
var transformedBox = getBoundingBox(transformPoint(topLeft), transformPoint(topRight), transformPoint(bottomLeft), transformPoint(bottomRight));
console.log(transformedBox);
box.style.transform = 'matrix('+matrix.join()+')';
var endingRect = box.getBoundingClientRect();
console.log(endingRect);
输出:
{x: 158, y: 400, width: 150, height: 150}
DOMRect {x: 158, y: 400, width: 150, height: 150, top: 400, …}
如评论中所述,您必须简单地乘以矩阵(您可以只做 2 x 2 版本,然后添加平移变量。)但正如 Olivier 所述,您需要先平移坐标,然后将它们翻译回去。这很简单,只需计算中心即可。
我会把它写成一个函数,从一个矩形和矩阵(作为用于 CSS 转换的六个变量的平面数组)到另一个矩形,一个简单的 {left, right, top, bottom}
矩形或如果需要,一个 DomRect。
您可以在此代码段中看到它(如果使用“整页”展开它会更容易看到 link):
const transformRect = (rect, matrix) => {
const {left, top, right, bottom} = rect
const [a, b, c, d, tx, ty] = matrix
const dx = (left + right) / 2, dy = (top + bottom) / 2
const newCorners = [[left, top], [right, top], [right, bottom], [left, bottom]]
.map (([x, y]) => [
a * (x - dx) + c * (y - dy) + tx + dx,
b * (x - dx) + d * (y - dy) + ty + dy
])
const _left = Math .min (... newCorners .map (p => p [0]))
const _right = Math .max (... newCorners .map (p => p [0]))
const _top = Math .min (... newCorners .map (p => p [1]))
const _bottom = Math .max (... newCorners .map (p => p [1]))
return DOMRect.fromRect (
{x: _left, y: _top, width: _right - _left, height: _bottom - _top}
) // or just
// return {x: _left, y: _top, width: _right - _left, height: _bottom - _top}
}
const div = document .getElementById ('d2')
const rect = div.getBoundingClientRect()
console .log ('Before:' , rect)
const matrix = [2, 1, -1, 2, 200, 400]
div.style.transform = `matrix(${matrix .join (', ')})`
console .log ('After:', transformRect (rect, matrix))
.box {
height: 50px;
width: 50px;
background: green;
color: white;
}
#d1 {
background: #ccc;
position: absolute;
top: 8;
left: 8;
}
<div id="d1" class="box">shadow</div>
<div id="d2" class="box">content</div>