如何在一个 `div` 上构建一个 SVG 叠加层,以使每一侧都成为一个放置目标?

huangapple go评论53阅读模式
英文:

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 叠加层,以使每一侧都成为一个放置目标?

而这个问题可能导致了第二个问题,当div相对于其宽度非常短或非常高时,位于长边上的嵌套SVG开始收缩(可能是因为它们撞到了外部SVG的另一侧,而不是溢出)。以下是我将上述代码中的#overlaidheight设置为200px时所得到的效果:

如何在一个 `div` 上构建一个 SVG 叠加层,以使每一侧都成为一个放置目标?

那么,如何将重叠的三角形剪切成中心的梯形以防止它们重叠,并且如何使三角形溢出外部的SVG而不是收缩?

也欢迎其他方法,但请记住,覆盖的矩形大小是任意的,并且放置目标需要能够接收放置事件。我还将要对这些放置目标应用样式,以指示它们何时处于活动状态以及何时被悬停在上面,所以请确保您的方法不会妨碍到这一点。

以下是当其高度小于宽度时(在这种情况下是400x200)它应该是什么样子的。
如何在一个 `div` 上构建一个 SVG 叠加层,以使每一侧都成为一个放置目标?

以下是当外部的div被拉伸以使其成为正方形(在这种情况下是400x400)时它应该是什么样子的。
如何在一个 `div` 上构建一个 SVG 叠加层,以使每一侧都成为一个放置目标?

最后,以下是当外部的div被拉伸以使其高度超过宽度时(在这种情况下是400x800)它应该是什么样子的。
如何在一个 `div` 上构建一个 SVG 叠加层,以使每一侧都成为一个放置目标?

英文:

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:


&lt;!-- begin snippet: js hide: false console: true babel: false --&gt;

&lt;!-- language: lang-css --&gt;

    #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;
    }

&lt;!-- language: lang-html --&gt;

    &lt;div id=&quot;overlaid&quot;&gt;
      &lt;svg id=&quot;overlay&quot;&gt;

        &lt;svg viewbox=&quot;0 0 100 50&quot; preserveAspectRatio=&quot;xMidYMin&quot;&gt;
          &lt;path id=&quot;above-zone&quot; d=&quot;M 0,0
                                   L 100,0 50,50
                                   z&quot; fill=&quot;rgba(0,127,255,0.5)&quot; /&gt;
        &lt;/svg&gt;

        &lt;svg viewbox=&quot;0 0 50 100&quot; preserveAspectRatio=&quot;xMinYMid&quot;&gt;
          &lt;path id=&quot;before-zone&quot; d=&quot;M 0,0
                                    L 50,50 0,100
                                    z&quot; fill=&quot;rgba(255,0,127,0.5)&quot; /&gt;
        &lt;/svg&gt;

        &lt;svg viewbox=&quot;0 0 50 100&quot; preserveAspectRatio=&quot;xMaxYMid&quot;&gt;
          &lt;path id=&quot;after-zone&quot; d=&quot;M 50,0
                                   L 50,100 0,50
                                   z&quot; fill=&quot;rgba(0,255,127,0.5)&quot; /&gt;
        &lt;/svg&gt;

        &lt;svg viewbox=&quot;0 0 100 50&quot; preserveAspectRatio=&quot;xMidYMax&quot;&gt;
          &lt;path id=&quot;below-zone&quot; d=&quot;M 0,50
                                   L 50,0 100,50
                                   z&quot; fill=&quot;rgba(255,127,0,0.5)&quot; /&gt;
        &lt;/svg&gt;

      &lt;/svg&gt;
    &lt;/div&gt;

&lt;!-- end snippet --&gt;

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:

![Top and bottom triangles overlap. How do I clip them into trapezoids so they don't overlap?](https://i.stack.imgur.com/rhtTy.png "Overlapping triangles")

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:

![This is what I get when the height is very small with respect to the width. How do I get the triangles to overflow instead of shrink?](https://i.stack.imgur.com/DPbgZ.png "Gaps between triangles")

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).
如何在一个 `div` 上构建一个 SVG 叠加层,以使每一侧都成为一个放置目标?

Here's what it should look like when the outer div is stretched taller to make it square (400x400 in this case).
如何在一个 `div` 上构建一个 SVG 叠加层,以使每一侧都成为一个放置目标?

And finally, here's what it should look like when the outer div is stretched taller than it is wide (400x800 in this case).
如何在一个 `div` 上构建一个 SVG 叠加层,以使每一侧都成为一个放置目标?

答案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(&#39;#overlay&#39;);
const feedback = document.querySelector(&#39;#feedback&#39;)

overlay.addEventListener(&#39;mouseover&#39;, (event) =&gt; {
  const side = [...event.target.classList]
               .find(cl =&gt; cl.endsWith(&#39;zone&#39;));
  if (side) {
    feedback.textContent = side;
  }
});

overlay.addEventListener(&#39;mouseleave&#39;, (event) =&gt; {
  feedback.textContent = &#39;&#39;;
});

<!-- 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 -->

&lt;div id=&quot;overlaid&quot;&gt;
  &lt;svg id=&quot;overlay&quot;&gt;

    &lt;rect class=&quot;wide above-zone&quot; width=&quot;100%&quot; height=&quot;50%&quot; /&gt;

    &lt;rect class=&quot;wide below-zone&quot; y=&quot;50%&quot; width=&quot;100%&quot; height=&quot;50%&quot; /&gt;

    &lt;rect class=&quot;high before-zone&quot; width=&quot;50%&quot; height=&quot;100%&quot; /&gt;

    &lt;rect class=&quot;high after-zone&quot; x=&quot;50%&quot; width=&quot;50%&quot; height=&quot;100%&quot; /&gt;

    &lt;svg class=&quot;wide&quot; viewBox=&quot;0 0 50 100&quot; preserveAspectRatio=&quot;xMinYMid&quot;&gt;
      &lt;path class=&quot;before-zone&quot; d=&quot;M 0,0 L 50,50 0,100 z&quot; /&gt;
    &lt;/svg&gt;

    &lt;svg class=&quot;wide&quot; viewBox=&quot;0 0 50 100&quot; preserveAspectRatio=&quot;xMaxYMid&quot;&gt;
      &lt;path class=&quot;after-zone&quot; d=&quot;M 50,0 L 50,100 0,50 z&quot; /&gt;
    &lt;/svg&gt;

    &lt;svg class=&quot;high&quot; viewBox=&quot;0 0 100 50&quot; preserveAspectRatio=&quot;xMidYMin&quot;&gt;
      &lt;path class=&quot;above-zone&quot; d=&quot;M 0,0 L 50,50 100,0 z&quot; /&gt;
    &lt;/svg&gt;

    &lt;svg class=&quot;high&quot; viewBox=&quot;0 0 100 50&quot; preserveAspectRatio=&quot;xMidYMax&quot;&gt;
      &lt;path class=&quot;below-zone&quot; d=&quot;M 0,50 L 100,50 50,0 z&quot; /&gt;
    &lt;/svg&gt;

  &lt;/svg&gt;
&lt;/div&gt;

&lt;p id=&quot;feedback&quot;&gt;

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年4月20日 06:53:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/76059370.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定