英文:
How to construct an SVG overlay over a div to give each side a drop target?
问题
我试图创建一个覆盖层,放置在一个任意大小的div
上,为其提供四个放置目标——每个对应于div
矩形的一侧。放置目标的几何形状应该使得被放置的元素会移动到离div
最近的一侧。这意味着一个矮而宽的div
将会有梯形状的顶部和底部放置目标,以及三角形状的左侧和右侧放置目标,而一个高而窄的div
将会有三角形状的顶部和底部放置目标,以及梯形状的左侧和右侧放置目标(一个正方形的div
将会是全都是三角形)。
以下是我尝试用于覆盖层的嵌套SVG以及它所覆盖的HTML和相关的CSS:
#overlaid {
position: relative;
width: 450px;
height: 300px;
border: 5px solid lightgray;
}
#overlay {
--overlay-visibility: visible;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 999999;
visibility: var(--overlay-visibility, hidden);
}
svg {
stroke: black;
}
path:hover {
fill: yellow;
}
<div id="overlaid">
<svg id="overlay">
<svg viewbox="0 0 100 50" preserveAspectRatio="xMidYMin">
<path id="above-zone" d="M 0,0 L 100,0 50,50 z" fill="rgba(0,127,255,0.5)" />
</svg>
<svg viewbox="0 0 50 100" preserveAspectRatio="xMinYMid">
<path id="before-zone" d="M 0,0 L 50,50 0,100 z" fill="rgba(255,0,127,0.5)" />
</svg>
<svg viewbox="0 0 50 100" preserveAspectRatio="xMaxYMid">
<path id="after-zone" d="M 50,0 L 50,100 0,50 z" fill="rgba(0,255,127,0.5)" />
</svg>
<svg viewbox="0 0 100 50" preserveAspectRatio="xMidYMax">
<path id="below-zone" d="M 0,50 L 50,0 100,50 z" fill="rgba(255,127,0,0.5)" />
</svg>
</svg>
</div>
我对此有两个问题,我不知道如何解决。首先,当任意大小的div
不是正方形时(它可以是任何大小,并且可能会改变),我不知道如何将长边上的三角形剪切成中心的梯形。以下是使用上述代码时我得到的效果:
而这个问题可能导致了第二个问题,当div
相对于其宽度非常短或非常高时,位于长边上的嵌套SVG开始收缩(可能是因为它们撞到了外部SVG的另一侧,而不是溢出)。以下是我将上述代码中的#overlaid
的height
设置为200px
时所得到的效果:
那么,如何将重叠的三角形剪切成中心的梯形以防止它们重叠,并且如何使三角形溢出外部的SVG而不是收缩?
也欢迎其他方法,但请记住,覆盖的矩形大小是任意的,并且放置目标需要能够接收放置事件。我还将要对这些放置目标应用样式,以指示它们何时处于活动状态以及何时被悬停在上面,所以请确保您的方法不会妨碍到这一点。
以下是当其高度小于宽度时(在这种情况下是400x200)它应该是什么样子的。
以下是当外部的div
被拉伸以使其成为正方形(在这种情况下是400x400)时它应该是什么样子的。
最后,以下是当外部的div
被拉伸以使其高度超过宽度时(在这种情况下是400x800)它应该是什么样子的。
英文:
I'm trying to create an overlay to place over an arbitrarily-sized div
to provide it with four drop targets—each one corresponding to a side of the div
's rectangle. The geometry of the drop targets should be such that a dropped element will go to the closest side of the div. This means a short, wide div
will have trapezoidal top and bottom drop targets and triangular left and right drop targets, whereas a tall, narrow div will have triangular top and bottom drop targets and trapezoidal left and right drop targets (and a square div will have all triangles).
Here is the nested SVG I am trying to use for my overlay and the HTML it is overlaying along with its CSS:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-css -->
#overlaid {
position: relative;
width: 450px;
height: 300px;
border: 5px solid lightgray;
}
#overlay {
--overlay-visibility: visible;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 999999;
visibility: var(--overlay-visibility, hidden);
}
svg {
stroke: black;
}
path:hover {
fill: yellow;
}
<!-- language: lang-html -->
<div id="overlaid">
<svg id="overlay">
<svg viewbox="0 0 100 50" preserveAspectRatio="xMidYMin">
<path id="above-zone" d="M 0,0
L 100,0 50,50
z" fill="rgba(0,127,255,0.5)" />
</svg>
<svg viewbox="0 0 50 100" preserveAspectRatio="xMinYMid">
<path id="before-zone" d="M 0,0
L 50,50 0,100
z" fill="rgba(255,0,127,0.5)" />
</svg>
<svg viewbox="0 0 50 100" preserveAspectRatio="xMaxYMid">
<path id="after-zone" d="M 50,0
L 50,100 0,50
z" fill="rgba(0,255,127,0.5)" />
</svg>
<svg viewbox="0 0 100 50" preserveAspectRatio="xMidYMax">
<path id="below-zone" d="M 0,50
L 50,0 100,50
z" fill="rgba(255,127,0,0.5)" />
</svg>
</svg>
</div>
<!-- end snippet -->
I have two issues with this that I don't know how to solve. First, when the arbitrarily-sized div
is not square (it could be any size—and could change), I don't know how to clip the triangles on the longer sides into trapezoids down the center of the div
. This is what I get with the above code:
And that issue may be leading to the second issue, which is when the div
is either very short or very tall with respect to its width, the nested SVGs on the longer sides begin to shrink (presumably because they bump into the other side of the outer SVG (instead of overflowing). This is what I get when I set the #overlaid
height
above to 200px
in the above code:
So, how do I clip overlapping triangles into trapezoids so they don't overlap down the middle, and how do I get the triangles to overflow the outer SVG instead of shrink?
Other approaches are welcome, too, but remember, the overlaid rectangle size is arbitrary and the drop targets need to be able to receive drop events. I am also going to want to apply styling to these drop targets to indicate when they are active and to indicate when they are being hovered over, so please make sure your approach does not prevent that.
Here's what it should look like when it is shorter than it is wide (400x200 in this case).
Here's what it should look like when the outer div
is stretched taller to make it square (400x400 in this case).
And finally, here's what it should look like when the outer div
is stretched taller than it is wide (400x800 in this case).
答案1
得分: 1
以下是您要翻译的内容:
"如果形状重叠并不是问题,因为指针事件只会由最上层的处理。因此,您唯一需要关心的是,形状被堆叠在这样一种方式上,以便对于每一边,预期的三角形和梯形区域位于所有其他区域之上。
为了抵消左右两侧三角形的重叠,如果div的宽高比低于1,则将它们与顶部和底部的矩形交换位置。为使此操作生效,您需要利用新的容器查询功能。它现在在所有浏览器中实现了,但请务必检查最低版本。
const overlay = document.querySelector('#overlay');
const feedback = document.querySelector('#feedback')
overlay.addEventListener('mouseover', (event) => {
const side = [...event.target.classList]
.find(cl => cl.endsWith('zone'));
if (side) {
feedback.textContent = side;
}
});
overlay.addEventListener('mouseleave', (event) => {
feedback.textContent = '';
});
#overlaid {
position: relative;
width: 450px;
height: 100px;
resize: both;
overflow: hidden;
border: 5px solid lightgray;
container-type: size;
}
#overlay {
--overlay-visibility: visible;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 999999;
visibility: var(--overlay-visibility, hidden);
}
.wide {
display: inline;
}
.high {
display: none;
}
@container (max-aspect-ratio: 1/1) {
.wide {
display: none;
}
.high {
display: inline;
}
}
path, rect {
stroke: black;
vector-effect: non-scaling-stroke;
}
.above-zone {
fill: rgba(0,127,255,0.5);
}
.below-zone {
fill: rgba(255,127,0,0.5);
}
.before-zone {
fill: rgba(255,0,127,0.5);
}
.after-zone {
fill: rgba(0,255,127,0.5);
}
path:hover, rect:hover {
fill: yellow;
}
<div id="overlaid">
<svg id="overlay">
<rect class="wide above-zone" width="100%" height="50%" />
<rect class="wide below-zone" y="50%" width="100%" height="50%" />
<rect class="high before-zone" width="50%" height="100%" />
<rect class="high after-zone" x="50%" width="50%" height="100%" />
<svg class="wide" viewBox="0 0 50 100" preserveAspectRatio="xMinYMid">
<path class="before-zone" d="M 0,0 L 50,50 0,100 z" />
</svg>
<svg class="wide" viewBox="0 0 50 100" preserveAspectRatio="xMaxYMid">
<path class="after-zone" d="M 50,0 L 50,100 0,50 z" />
</svg>
<svg class="high" viewBox="0 0 100 50" preserveAspectRatio="xMidYMin">
<path class="above-zone" d="M 0,0 L 50,50 100,0 z" />
</svg>
<svg class="high" viewBox="0 0 100 50" preserveAspectRatio="xMidYMax">
<path class="below-zone" d="M 0,50 L 100,50 50,0 z" />
</svg>
</svg>
</div>
<p id="feedback">
英文:
It is not a problem if the shapes overlap, as pointer events will only be handled by the uppermost one. So all you have to care about is that shapes are stacked in such a way that for each side the intended triangular and trapezoid areas are on top of all others.
To counteract the overlap of the triangles at the left and right sides, they are swapped with the top and bottom rectangles if the aspect ratio of the div gets below 1. For this to work, you have to take advantage of the new container query feature. It is now implemented in all browsers, but be sure to check minimum versions.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const overlay = document.querySelector('#overlay');
const feedback = document.querySelector('#feedback')
overlay.addEventListener('mouseover', (event) => {
const side = [...event.target.classList]
.find(cl => cl.endsWith('zone'));
if (side) {
feedback.textContent = side;
}
});
overlay.addEventListener('mouseleave', (event) => {
feedback.textContent = '';
});
<!-- language: lang-css -->
#overlaid {
position: relative;
width: 450px;
height: 100px;
resize: both;
overflow: hidden;
border: 5px solid lightgray;
container-type: size;
}
#overlay {
--overlay-visibility: visible;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 999999;
visibility: var(--overlay-visibility, hidden);
}
.wide {
display: inline;
}
.high {
display: none;
}
@container (max-aspect-ratio: 1/1) {
.wide {
display: none;
}
.high {
display: inline;
}
}
path, rect {
stroke: black;
vector-effect: non-scaling-stroke;
}
.above-zone {
fill: rgba(0,127,255,0.5);
}
.below-zone {
fill: rgba(255,127,0,0.5);
}
.before-zone {
fill: rgba(255,0,127,0.5);
}
.after-zone {
fill: rgba(0,255,127,0.5);
}
path:hover, rect:hover {
fill: yellow;
}
<!-- language: lang-html -->
<div id="overlaid">
<svg id="overlay">
<rect class="wide above-zone" width="100%" height="50%" />
<rect class="wide below-zone" y="50%" width="100%" height="50%" />
<rect class="high before-zone" width="50%" height="100%" />
<rect class="high after-zone" x="50%" width="50%" height="100%" />
<svg class="wide" viewBox="0 0 50 100" preserveAspectRatio="xMinYMid">
<path class="before-zone" d="M 0,0 L 50,50 0,100 z" />
</svg>
<svg class="wide" viewBox="0 0 50 100" preserveAspectRatio="xMaxYMid">
<path class="after-zone" d="M 50,0 L 50,100 0,50 z" />
</svg>
<svg class="high" viewBox="0 0 100 50" preserveAspectRatio="xMidYMin">
<path class="above-zone" d="M 0,0 L 50,50 100,0 z" />
</svg>
<svg class="high" viewBox="0 0 100 50" preserveAspectRatio="xMidYMax">
<path class="below-zone" d="M 0,50 L 100,50 50,0 z" />
</svg>
</svg>
</div>
<p id="feedback">
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论