Juan Martin
Juan Martin

Reputation: 149

Problem maintaining same size and alignment between SVG and border width with different resolutions/scaling

I have to make a speech bubble of sorts, and I've been provided an SVG for the part of the bubble that would point to the speaker. I'm trying to make the box itself a regular bordered div (this div will have dynamic height and width based on its contents, however the SVG will remain the same size).

I've done something similar to this stackblitz example: https://stackblitz.com/edit/angular-ivy-64a8xk which is to position the svg absolute within the div element and move it so that the SVG path is aligned to the border of the div element.

The problem:

This looks fine on a Macbook Pro 2560x1600: 2560x1600 Macbook example

But when using external monitors or even a Windows PC with a 2560x1440 monitor the SVG doesn't look fully aligned.

2560x1440 Windows example

The problem is even more evident when using browser zoom. This is how it looks on that Windows PC at 75% zoom:

2560x1440 Windows 75% zoom example

Here it looks like the border is reduced to 1px width, so the SVG path looks not only not aligned but also the incorrect size.

Is there a way to make the SVG behave the same way as the border-width so that they are always aligned and the same size? Maybe some other way to achieve the same effect?

Upvotes: 0

Views: 1103

Answers (3)

herrstrietzel
herrstrietzel

Reputation: 17240

You might also achieve a consistent border rendering by using a responsive/flexible background svg:

In this case you won't apply a viewBox property.
The <rect> element (with rounded corners via rx and ry will need a y offset to give enough space for your bubble's "tail").
The outer bubble border element is just stretched via a relative/absolute position context.

:root {
  --stroke-width: 2;
  --stroke-width-bg: 4;
  --stroke-color: orange;
  --bubble-bg:#fff;
}

* {
  box-sizing: border-box;
}

body {
  font-size: 5vmin;
  line-height: 1.35em;
  font-family: "Segoe UI", sans-serif;
}

.resize {
  resize: both;
  position: relative;
  border: 1px solid #ccc;
  overflow: auto;
  padding: 1em;
  width: 75vw;
  margin: 0 auto;
}

.text-box {
  position: relative;
  border: none;
  overflow: visible;
  width: 100%;
  padding: 40px 0 1em 0;
  margin-bottom: 40px;
}
.text-box p {
  margin: 0;
}

.text-box-left .svgBG {
  transform: scale(-1, 1);
  --stroke-color: blue;
  --bubble-bg: #9edbfa;
}

.text-content {
  position: relative;
  padding-top: 3em;
  padding: 0 1.5em;
  z-index: 10;
}

.svgBG {
  position: absolute;
  z-index: 1;
  top: 0;
  bottom: 0;
  height: 100%;
  width: 100%;
  overflow: visible;
  padding-bottom: 1em;
}
<div class="resize">
  <div class="text-box">
    <svg class="svgBG">
      <use href="#bubbleOuter" />
    </svg>
    <div class="text-content">
      <h3>mdn webdocs: vector-effect</h3>
      <p>The vector-effect property specifies the vector effect to use when drawing an object. Vector effects are applied before any of the other compositing operations, i.e. filters, masks and clips. <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/vector-effect">readmore</a></p>
    </div>
  </div>
  <div class="text-box text-box-left">
    <svg class="svgBG">
      <use href="#bubbleOuter" />
    </svg>
    <div class="text-content">
      <h3>mdn webdocs: viewBox</h3>
      <p>The viewBox attribute defines the position and dimension, in user space, of an SVG viewport.
        The value of the viewBox attribute is a list of four numbers: min-x, min-y, width and height. The numbers, which are separated by whitespace and/or a comma, specify a rectangle in user space which is mapped to the bounds of the viewport established for the associated SVG element (not the browser viewport). <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox">readmore</a></p>
    </div>
  </div>
<p style="text-align:right">resize me</p>
</div>

<svg class="svgAsset" style="position:absolute; width:0; height:0; visibility:hidden" overflow="visible">
  <symbol viewBox="0 0 43 43" id="bubble">
    <path d="M43 37  C38 37 34 29.6686 26 22.4215 C18 15.1745 7.5 8.34577 5 10.3566 C2.5 12.3674 5.5 15.8864 6 24.9351 C6.5 33.9838 5 37 5 37  z" />
  </symbol>
  <symbol id="bubbleOuter" overflow="visible">
    <use class="bubble-bg" href="#bubble" x="40" y="-9" width="43" height="43" style="stroke-width:var(--stroke-width-bg); stroke:var(--stroke-color); fill:var(--bubble-bg)" />
    <rect fill="#fff" stroke="#000" x="0" y="25" rx="8" ry="8" width="100%" height="100%" style="stroke-width:var(--stroke-width); stroke:var(--stroke-color); fill:var(--bubble-bg)" />
    <use href="#bubble" x="40" y="-9" width="43" height="43" style="fill:var(--bubble-bg)" />
  </symbol>
</svg>

However: This approach will require quite a lot of tweaking concerning padding and margins (depending on your svg graphics, layout etc).

Codepen Example

Upvotes: 1

Paul LeBeau
Paul LeBeau

Reputation: 101820

I'm not sure there is much you can do about this. SVGs have behaviour, around scaling and anti-aliasing, that is different to how browsers handle border width scaling.

One solution - that you probably won't like much - is to get the SVG to behave similarly to how the borders behave, by turning off anti-aliasing. You can do this by adding shape-rendering="crispEdges" to the <path>.

<path
      d="M43 37L38 37C38 37 34 29.6686 26 22.4215C18 15.1745 7.5 8.34577 5 10.3566C2.5 12.3674 5.5 15.8864 6 24.9351C6.5 33.9838 5 37 5 37L0 37"
      stroke="currentcolor"
      stroke-width="2"
      shape-rendering="crispEdges"
    />

The SVG stroke will stay much closer to the border width in size. But the curvy shape of the path will now look unpleasantly "jaggy".

My advice: switch the whole speech bubble to the same technique. Either both HTML, or both SVG.

Or just live with the slight difference.

Upvotes: 0

DvdRom
DvdRom

Reputation: 788

The Browser doesn't render sub-pixels on borders but does on SVG. That means at 125% DPI Scaling your border is technically 2.5px, but get's rounded down to 2px.

One way to solve this, it by adding resolution media queries.

@media (min-resolution: 120dpi) {
  .box {
    border-width: 2.5px;
  }
}

Demo: https://stackblitz.com/edit/angular-ivy-vbqtnz

Here is a short list of the scaling levels and the corresponding dpi values:

  • Smaller 100% (default) = 96 dpi
  • Medium 125% = 120 dpi
  • Larger 150% = 144 dpi
  • Extra Large 200% = 192 dpi

Upvotes: 0

Related Questions