Изменение толщины обводки svg треугольника только внутрь относительно осевой линии stroke

Есть равносторонний треугольник с произвольными координатами.

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
<polygon fill="none" stroke-width="4" stroke="red" points="200 35.45517328095667,10 364.54482671904333,390 364.54482671904333"></polygon>
</svg>

https://jsfiddle.net/6sybjthL/1/

Из-за особенностей svg, при изменении толщины обводки (stroke-width) идёт расширение одновременно и внутрь, и наружу. На сколько мне известно, для svg версии 1.1 нет какого-то свойства, которое это изменяет, поможет только геометрия и математика.

Отсюда 2 вопроса:

  1. Какую формулу применять для пересчёта координат треугольника при изменении толщины обводки на произвольное значение, чтобы треугольник оставался равносторонним и не расширялся визуально наружу (то есть происходит заполнение фона треугольника)?.

  2. Как посчитать максимальный размер обводки, при которой треугольник будет полностью заполнен за счёт такой обводки только внутрь?

UPD

Ниже пример моих попыток пересчитать координаты для нового значения stroke-width=40. (цель сделать так, чтобы зеленый треугольник не выходил за красный)

https://jsfiddle.net/xqv3ms8b/9/


Ответы (2 шт):

Автор решения: Alexandr_TT

Решение SVG + Mask

Идея такая:

Чтобы обрезать внешнюю половину строки и не мешать росту внутренней половине строки при увеличении stroke-width, воспользуемся mask SVG

Фигура (любая) добавляется в mask в секции defs
У path этой фигуры назначаются стили fill="white" и stroke="white" Это сделает маску, которая будет показывать изначально шаблон исходной фигуры с постоянной шириной строки. Излишки внешней половины строки, при увеличении stroke-width будут обрезаться вторым элементом маски <rect> Внутренняя половина строки будет увеличиваться без ограничений.

mask

<defs>
<mask id="msk">
<rect width="100%" height="100%" fill="black" />
<polygon  fill="white" stroke-width="8" stroke="black" points="200 35.5 10 364.5 390 364.5"></polygon>
</mask> 
</defs> 

Вторую фигуру, у которой будем изменять stroke-width с точно таким же path, как и фигура в mask помещаем ниже в теле фигуры и применяем к ней маску mask="url(#msk)" Внешняя часть строки будет обрезана маской, а внутренняя часть будет показана без обрезки, то есть будет увеличиваться при увеличении stroke-width

  • Ниже в программе добавлен круг с постоянными атрибутами, который описывает исходную фигуру, показывая тем самым, что габаритные размеры фигуры остаются без изменений при увеличении stroke-width
  • JS добавлен только, для демонстрации работы mask по обрезке внешней стороны stroke-width

function changeStroke() {
var sw = document.getElementById("poly"); 
   itr.addEventListener("input",()=>{sw.setAttribute("stroke-width",itr.value)});
    var x = document.getElementById("itr").value;
    
    document.getElementById("infoSW").innerHTML = "stroke-width: " + x +"px";
}
<div>
 <input id="itr" type="range" min="4" value="4" max="150"  oninput="changeStroke()"/>

</div>
<div><text id="infoSW">stroke-width:4px </text></div>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="500" height="400" viewBox="0 0 400 500">
<defs>
<mask id="msk">
<rect width="100%" height="100%" fill="black" />
<polygon  fill="white" stroke-width="8" stroke="white" points="200 35.5 10 364.5 390 364.5"></polygon>
</mask> 

</defs> 

    <polygon id="poly" mask="url(#msk)" fill="yellowgreen" stroke-width="4" stroke="red" points="200 35.5 10 364.5 390 364.5"></polygon>
      <circle cx="200" cy="252" r="4" fill="black" stroke="black" />
    <circle cx="200" cy="252" r="224" fill="none" stroke="grey" stroke-width="4" />
</svg>

Чтобы показать, что данная техника обрезки внешней части строки универсальна, добавлю более сложный элемент

function changeStroke() {
var sw = document.getElementById("poly"); 
     itr.addEventListener("input",()=>{sw.setAttribute("stroke-width",itr.value)});
    var x = document.getElementById("itr").value;
    
    document.getElementById("infoSW").innerHTML = "stroke-width: " + x +"px";
}
<div>
 <input id="itr" type="range" min="4" value="4" max="20"  oninput="changeStroke()"/>
</div>
<div><text id="infoSW">stroke-width:4px </text></div>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="500" height="400" viewBox="40 200 100 125">
<defs>
<mask id="msk">
<rect width="100%" height="100%" fill="black" />
<path fill="white" stroke-width="4" stroke="white" d="m32.5 272c-16.3-5.6-11.4-33.8 0-31.4 0-10.6 13.4-18.7 22.8-9.4 0 0 2.4-13.4 21.1-13.4 19.1 0.1 22.6 21.2 22.6 21.2 15.8-1.3 18.6 24.6 4.8 32.6z"
</mask> 

</defs> 
    <path id="poly" mask="url(#msk)" fill="white" stroke="skyblue" stroke-width="4" stroke-linecap="round"  stroke-linejoin="round" d="m32.5 272c-16.3-5.6-11.4-33.8 0-31.4 0-10.6 13.4-18.7 22.8-9.4 0 0 2.4-13.4 21.1-13.4 19.1 0.1 22.6 21.2 22.6 21.2 15.8-1.3 18.6 24.6 4.8 32.6z"
</svg>

→ Ссылка
Автор решения: Alexandr_TT

Есть ещё один способ избежать дополнительного увеличения габаритных размеров фигуры за счет прироста внешней половины толщины строки, при масштабировании scale(X,Y)

В SVG есть атрибут строки vector-effect1="non-scaling-stroke"

Этот атрибут предотвращает увеличение ширины строки при увеличении или уменьшении элемента SVG.

Другими словами, как не изменяй размеры фигуры с помощью scale() или viewBox, строка не изменит свою ширину.

#1. Пример без vector-effect1="non-scaling-stroke"

При увеличении размеров фигуры, увеличивается толщина stroke

Анимация начнется после клика по холсту:

<svg id="svg1" width="250" height="250" version="1.1"
viewBox="0 0 250 250" 
      xmlns="http://www.w3.org/2000/svg" style="border:1px solid" >
    <rect x="10" y="10 "width="50" height="50" rx="10" fill="none" stroke="crimson" stroke-width="4">
     <animateTransform
       attributeName="transform"
       type="scale"
       values="1;1.5;2;2.5;3;3.5;4"
       calcMode="discrete"
       begin="svg1.click"
       dur="6s"
       repeatCount="1"
       fill="freeze"
      </rect>      
      </svg>

#2. Пример с использованием vector-effect1="non-scaling-stroke"

<svg id="svg1" width="250" height="250" version="1.1"
viewBox="0 0 250 250" 
      xmlns="http://www.w3.org/2000/svg" style="border:1px solid" >
    <rect x="10" y="10 "width="50" height="50" rx="10" fill="none" stroke="crimson" stroke-width="4" vector-effect="non-scaling-stroke" >
     <animateTransform
       attributeName="transform"
       type="scale"
       values="1;1.5;2;2.5;3;3.5;4"
       calcMode="discrete"
       begin="svg1.click"
       dur="6s"
       repeatCount="1"
       fill="freeze" />
      </rect>      
 </svg>

Обратите внимание, что фигура заняла в финальном положении несколько меньшее место по сравнению с первым примером, за счет того, что не увеличилась толщина строки

→ Ссылка