如何创建一个带边框的方圆?

Gre*_*lff 4 html css svg reactjs

有没有什么方法可以制作一个松鼠,同时让边框、阴影或内部发光也符合松鼠的形状?

创建圆角可以通过 CSS 使用 houdini 来完成,但是浏览器的支持paintWorklet非常差,使用率仅为 71.35% (2023),可能是因为与绘制工作集有关的安全漏洞。

其他替代方案包括使用具有方圆形状的SVG<clipPath>,但添加方圆边框必须使用原始剪辑路径重新绘制,这使得转换变得困难。当元素缩放时,笔划路径和剪辑路径需要重新渲染。元素的 CSS 属性需要转换为路径数据。

<svg xmlns="http://www.w3.org/2000/svg" width="220" height="220" viewBox="-10 -10 220 220">
  <defs>
    <clipPath id="squircle-clip">
      <path d="M20,0
        L180,0
        Q200,0 200,20
        L200,180
        Q200,200 180,200
        L20,200
        Q0,200 0,180
        L0,20
        Q0,0 20,0"
        style="vector-effect: non-scaling-stroke;" 
      />
    </clipPath>
  </defs>
  <rect x="0" y="0" width="200" height="200" fill="#222" clip-path="url(#squircle-clip)"
  />
  <path d="M20,0
    L180,0
    Q200,0 200,20
    L200,180
    Q200,200 180,200
    L20,200
    Q0,200 0,180
    L0,20
    Q0,0 20,0" 
    fill="none" stroke="#484848" stroke-width="2" style="vector-effect: non-scaling-stroke;"
  />
</svg>
Run Code Online (Sandbox Code Playgroud)

是否有其他方法来创建带边框的方圆?Houdini 是一个难以选择的解决方案,因为它只拥有约 71% 的用户,并且不支持 Safari(iOS 和 macOS)或 Firefox。

her*_*zel 5

剪辑路径(以及蒙版)将剪辑笔划和滤镜

\n

如果您不需要任何笔画或滤镜/效果(例如投影),则使用剪辑路径效果很好。

\n

如果您的最终目标是创建类似 iOS 的 icon svg 可能是您的最佳选择

\n

\r\n
\r\n
.resize {\n  border: 1px solid #ccc;\n  resize: both;\n  overflow: auto;\n  width: 50%;\n  max-width: 50%;\n}\n\nsvg {\n  width: 100%;\n}\n\n.icon {\n  fill: orange;\n  stroke: #000;\n  stroke-width: 2px;\n  filter: drop-shadow(5px 5px 2px rgba(0, 0, 0, 0.75));\n}
Run Code Online (Sandbox Code Playgroud)\r\n
<h3>Resize me</h3>\n<div class="resize">\n  <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">\n    <path d="M 60 10 h 0 c 35.385 0 50 14.615 50 50 v 0 c 0 35.385 -14.615 50 -50 50 h 0 c -35.385 0 -50 -14.615 -50 -50 v 0 c 0 -35.385 14.615 -50 50 -50" />\n  </svg>\n</div>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

上面的方圆是用我创建的一个简单的助手生成的:
\n codepen“方圆生成器(回旋圆角)”

\n

如果您需要动态创建方圆(例如在绘图应用程序中),您可以将 svg<rect>与动态更新的<path>元素同步:

\n

调整大小时同步<path>动态更新

\n

您可以创建一个共享其宽度、高度和其他属性<path>的圆形<rect>元素的“克隆”。

\n

矩形是隐藏的,但对变换做出响应。
\n一旦矩形被转换,<path>d 属性就会根据当前的矩形尺寸重新计算。

\n

\r\n
\r\n
addClothoidPaths(2.5, 0.9, true);\n\ndocument.querySelectorAll(".inputs").forEach((input) => {\n  input.addEventListener("input", (e) => {\n    let r = +inputBorderRadius.value;\n    let tension = +inputTension.value;\n    let cubic = +document.querySelector(\'input[name="bezierType"]:checked\')\n      .value;\n    addClothoidPaths(r, tension, cubic);\n  });\n});\n\nfunction addClothoidPaths(borderRadius = 2.5, tension = 0.9, cubic = true) {\n  const ns = "http://www.w3.org/2000/svg";\n\n  let rects = document.querySelectorAll("rect");\n  rects.forEach((rect, i) => {\n    let svg = rect.closest("svg");\n\n    // create clothoid rounded path\n    let rectPathGroup = svg.querySelector(".rectGroup");\n    let rectPath = svg.querySelector(".rectPath" + i);\n\n    if (!rectPathGroup) {\n      rectPathGroup = document.createElementNS(ns, "g");\n\n      rectPathGroup.classList.add("rectGroup");\n      svg.append(rectPathGroup);\n\n      rectPath = document.createElementNS(ns, "path");\n      rectPath.classList.add("rectPath" + i);\n\n      rectPathGroup.append(rectPath);\n    }\n\n    //console.log(rectPath)\n\n    /**\n     * copy rect attributes\n     */\n    const setAttributes = (el, attributes, exclude = []) => {\n      for (key in attributes) {\n        if (exclude.indexOf(key) === -1) {\n          el.setAttribute(key, attributes[key]);\n        }\n      }\n    };\n\n    const getAttributes = (el) => {\n      let attArr = [...el.attributes];\n      let attObj = {};\n      attArr.forEach(function (att) {\n        attObj[att.nodeName] = att.nodeValue;\n      });\n      return attObj;\n    };\n\n    //exclude attributes not needed for paths\n    let exclude = ["x", "y", "r", "rx", "ry", "height", "width", "id"];\n    // copy attributes to path and set pathData\n    let attributes = getAttributes(rect);\n    setAttributes(rectPath, attributes, exclude);\n\n    //hide rect\n    rect.style.visibility = "hidden";\n\n    let d = updateClothoid(rect, borderRadius, tension);\n    rectPath.setAttribute("d", d);\n    rectPath.style.visibility = "visible";\n\n    let resizeObserver = new ResizeObserver((entries) => {\n      entries.forEach((entry) => {\n        let d = updateClothoid(entry.target, borderRadius, tension, cubic);\n        rectPath.setAttribute("d", d);\n        updateOutput();\n      });\n    });\n\n    // Observe one or multiple elements\n    resizeObserver.observe(rect);\n  });\n}\n\nfunction updateOutput() {\n  output.value = new XMLSerializer().serializeToString(svg);\n}\n\nfunction updateClothoid(rect, borderRadius, tension, cubic = true) {\n  let x = rect.x.baseVal.value;\n  let y = rect.y.baseVal.value;\n  let w = rect.width.baseVal.value;\n  let h = rect.height.baseVal.value;\n  let r = rect.rx.baseVal.value;\n  let rC = r * borderRadius;\n\n  let lineHLength = w - rC * 2;\n  let lineVLength = h - rC * 2;\n  let d = "";\n\n  // prevent border radius smaller than half width\n  if (rC > w / 2 || rC > h / 2) {\n    rC = Math.min(...[w, h]) / 3;\n    lineHLength = w - rC * 2;\n    lineVLength = h - rC * 2;\n  }\n\n  if (cubic) {\n    d = `\n    M ${x + rC} ${y}\n    h ${lineHLength}\n    c ${rC * tension} 0\n     ${rC} ${rC * (1 - tension)}  \n     ${rC} ${rC}  \n    v ${lineVLength}\n    c 0 ${rC * tension}  \n     -${rC * (1 - tension)} ${rC}  \n     -${rC} ${rC} \n     h -${lineHLength}\n    c -${rC * tension}  0\n     -${rC} -${rC * (1 - tension)}  \n     -${rC} -${rC} \n     v-${lineVLength}\n    c 0 -${rC * tension} \n     ${rC * (1 - tension)} -${rC}   \n     ${rC} -${rC}`;\n  }\n  // quadratic border smoothing\n  else {\n    d = `\n    M ${x + rC} ${y}\n    h ${lineHLength}\n    q ${rC} 0\n    ${rC} ${rC}  \n    v ${lineVLength}\n    q 0 ${rC}\n    -${rC} ${rC}  \n    h -${lineHLength}\n    q -${rC} 0\n    -${rC} -${rC}  \n    v -${lineVLength}\n    q  0 -${rC}\n     ${rC} -${rC}`;\n  }\n\n  return d.replace(/[\\n\\r\\t]/g, "").replace(/\\s{2,}/g, " ");\n}
Run Code Online (Sandbox Code Playgroud)\r\n
.resize {\n  border: 1px solid #ccc;\n  resize: both;\n  overflow: auto;\n  width: 50%;\n  max-width: 50%;\n}\n\nsvg {\n  display: block;\n  width: 100%;\n  height: 100%;\n}\n\ntextarea {\n  width: 100%;\n  min-height: 20em;\n}
Run Code Online (Sandbox Code Playgroud)\r\n
<p>Border-radius<input class="inputs" id="inputBorderRadius" type="range" min="1" max="5" step="0.1"></p>\n<p>Tension (only for cubic)<input class="inputs" id="inputTension" type="range" min="0.5" max="1" step="0.1"></p>\n\n<p><label>  <input class="inputs"  type="radio" name="bezierType" value="1" checked> Cubic</label> <label>  <input class="inputs" type="radio" name="bezierType" value="0"> Quadratic</label> </p>\n\n\n<h3>Resize me</h3>\n<div class="resize">\n  <svg id="svg">\n    <rect id="rect" x="10%" y="10%" width="80%" height="80%" rx="10" fill="#ccc" stroke="#000" stroke-width="10" transform="rotate(0)" transform-orgin="center" />\n  </svg>\n</div>\n\n<fieldset>\n  <legend>Output</legend>\n  <textarea id="output"></textarea>\n</fieldset>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

计算伪回旋曲线圆角

\n

基于 rect 初始rx边界半径属性,我们可以计算二次或三次曲线段,如下所示:

\n
function updateClothoid(rect, borderRadius, tension, cubic = true) {\n  let x = rect.x.baseVal.value;\n  let y = rect.y.baseVal.value;\n  let w = rect.width.baseVal.value;\n  let h = rect.height.baseVal.value;\n  let r = rect.rx.baseVal.value;\n  let rC = r * borderRadius;\n\n  // horizontal and vertical line segments between curves\n  let lineHLength = w - rC * 2;\n  let lineVLength = h - rC * 2;\n  let d = "";\n\n  // prevent border radius smaller than half width\n  if (rC > w / 2 || rC > h / 2) {\n    rC = Math.min(...[w, h]) / 3;\n    lineHLength = w - rC * 2;\n    lineVLength = h - rC * 2;\n  }\n\n  if (cubic) {\n    d = `\n    M ${x + rC} ${y}\n    h ${lineHLength}\n    c ${rC * tension} 0\n     ${rC} ${rC * (1 - tension)}  \n     ${rC} ${rC}  \n    v ${lineVLength}\n    c 0 ${rC * tension}  \n     -${rC * (1 - tension)} ${rC}  \n     -${rC} ${rC} \n     h -${lineHLength}\n    c -${rC * tension}  0\n     -${rC} -${rC * (1 - tension)}  \n     -${rC} -${rC} \n     v-${lineVLength}\n    c 0 -${rC * tension} \n     ${rC * (1 - tension)} -${rC}   \n     ${rC} -${rC}`;\n  }\n  // quadratic border smoothing\n  else {\n    d = `\n    M ${x + rC} ${y}\n    h ${lineHLength}\n    q ${rC} 0\n    ${rC} ${rC}  \n    v ${lineVLength}\n    q 0 ${rC}\n    -${rC} ${rC}  \n    h -${lineHLength}\n    q -${rC} 0\n    -${rC} -${rC}  \n    v -${lineVLength}\n    q  0 -${rC}\n     ${rC} -${rC}`;\n  }\n\n  // remove whitespace\n  return d.replace(/[\\n\\r\\t]/g, "").replace(/\\s{2,}/g, " ");\n}\n
Run Code Online (Sandbox Code Playgroud)\n

较高rC将增加初始边界半径以获得更平滑的曲线。
\n三次 B\xc3\xa9ziers 可以更好地控制曲率。

\n

较高的tension值会将控制点“拉”到角落,从而导致视觉上较小的边框半径。

\n

与基于添加圆弧的默认边界半径方法相比,这两个选项(二次和三次)都会在直线和曲线之间产生更平滑的过渡。

\n

\r\n
\r\n
function updateClothoid(rect, borderRadius, tension, cubic = true) {\n  let x = rect.x.baseVal.value;\n  let y = rect.y.baseVal.value;\n  let w = rect.width.baseVal.value;\n  let h = rect.height.baseVal.value;\n  let r = rect.rx.baseVal.value;\n  let rC = r * borderRadius;\n\n  // horizontal and vertical line segments between curves\n  let lineHLength = w - rC * 2;\n  let lineVLength = h - rC * 2;\n  let d = "";\n\n  // prevent border radius smaller than half width\n  if (rC > w / 2 || rC > h / 2) {\n    rC = Math.min(...[w, h]) / 3;\n    lineHLength = w - rC * 2;\n    lineVLength = h - rC * 2;\n  }\n\n  if (cubic) {\n    d = `\n    M ${x + rC} ${y}\n    h ${lineHLength}\n    c ${rC * tension} 0\n     ${rC} ${rC * (1 - tension)}  \n     ${rC} ${rC}  \n    v ${lineVLength}\n    c 0 ${rC * tension}  \n     -${rC * (1 - tension)} ${rC}  \n     -${rC} ${rC} \n     h -${lineHLength}\n    c -${rC * tension}  0\n     -${rC} -${rC * (1 - tension)}  \n     -${rC} -${rC} \n     v-${lineVLength}\n    c 0 -${rC * tension} \n     ${rC * (1 - tension)} -${rC}   \n     ${rC} -${rC}`;\n  }\n  // quadratic border smoothing\n  else {\n    d = `\n    M ${x + rC} ${y}\n    h ${lineHLength}\n    q ${rC} 0\n    ${rC} ${rC}  \n    v ${lineVLength}\n    q 0 ${rC}\n    -${rC} ${rC}  \n    h -${lineHLength}\n    q -${rC} 0\n    -${rC} -${rC}  \n    v -${lineVLength}\n    q  0 -${rC}\n     ${rC} -${rC}`;\n  }\n\n  // remove whitespace\n  return d.replace(/[\\n\\r\\t]/g, "").replace(/\\s{2,}/g, " ");\n}\n
Run Code Online (Sandbox Code Playgroud)\r\n
let d = updateClothoid(rect, 2.5, 0.9, true);\npath.setAttribute(\'d\', d)\n\nfunction updateClothoid(rect, borderRadius, tension, cubic = true) {\n  let x = rect.x.baseVal.value;\n  let y = rect.y.baseVal.value;\n  let w = rect.width.baseVal.value;\n  let h = rect.height.baseVal.value;\n  let r = rect.rx.baseVal.value;\n  let rC = r * borderRadius;\n\n  // horizontal and vertical line segments between curves\n  let lineHLength = w - rC * 2;\n  let lineVLength = h - rC * 2;\n  let d = "";\n\n  // prevent border radius smaller than half width\n  if (rC > w / 2 || rC > h / 2) {\n    rC = Math.min(...[w, h]) / 3;\n    lineHLength = w - rC * 2;\n    lineVLength = h - rC * 2;\n  }\n\n  if (cubic) {\n    d = `\n    M ${x + rC} ${y}\n    h ${lineHLength}\n    c ${rC * tension} 0\n     ${rC} ${rC * (1 - tension)}  \n     ${rC} ${rC}  \n    v ${lineVLength}\n    c 0 ${rC * tension}  \n     -${rC * (1 - tension)} ${rC}  \n     -${rC} ${rC} \n     h -${lineHLength}\n    c -${rC * tension}  0\n     -${rC} -${rC * (1 - tension)}  \n     -${rC} -${rC} \n     v-${lineVLength}\n    c 0 -${rC * tension} \n     ${rC * (1 - tension)} -${rC}   \n     ${rC} -${rC}`;\n  }\n  // quadratic border smoothing\n  else {\n    d = `\n    M ${x + rC} ${y}\n    h ${lineHLength}\n    q ${rC} 0\n    ${rC} ${rC}  \n    v ${lineVLength}\n    q 0 ${rC}\n    -${rC} ${rC}  \n    h -${lineHLength}\n    q -${rC} 0\n    -${rC} -${rC}  \n    v -${lineVLength}\n    q  0 -${rC}\n     ${rC} -${rC}`;\n  }\n\n  // remove whitespace\n  return d.replace(/[\\n\\r\\t]/g, "").replace(/\\s{2,}/g, " ");\n}
Run Code Online (Sandbox Code Playgroud)\r\n
svg {\n  display: block;\n  width: 20em;\n  border: 1px solid #ccc\n}
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

CSS 方法:用 CSS 剪辑路径包裹方圆

\n

您还可以使用 CSS 方圆生成器,例如“CSS Clothoid Corners”

\n

\r\n
\r\n
<svg id="svg" viewBox="0 0 100 100">\n    <rect id="rect" x="10%" y="10%" width="80%" height="80%" rx="10" fill="none" stroke="#ccc" stroke-width="0.5" transform="rotate(0)" transform-orgin="center" />\n    <path id="path" fill="none" stroke="red" stroke-width="0.75"/>\n  </svg>
Run Code Online (Sandbox Code Playgroud)\r\n
:root{\n  --clip: polygon(45.837405% 0%,\n        calc(100% - 45.837405%) 0%,\n        calc(100% - 41.024763%) 0.022716%,\n        calc(100% - 36.21469%) 0.166797%,\n        calc(100% - 31.418198%) 0.543282%,\n        calc(100% - 26.661607%) 1.261583%,\n        calc(100% - 22.002771%) 2.456312%,\n        calc(100% - 17.530819%) 4.217852%,\n        calc(100% - 13.363987%) 6.605826%,\n        calc(100% - 9.657177%) 9.657177%,\n        calc(100% - 6.605826%) 13.363987%,\n        calc(100% - 4.217852%) 17.530819%,\n        calc(100% - 2.456312%) 22.002771%,\n        calc(100% - 1.261583%) 26.661607%,\n        calc(100% - 0.543282%) 31.418198%,\n        calc(100% - 0.166797%) 36.21469%,\n        calc(100% - 0.022716%) 41.024763%,\n        calc(100% - 0.022716%) calc(100% - 41.024763%),\n        calc(100% - 0.166797%) calc(100% - 36.21469%),\n        calc(100% - 0.543282%) calc(100% - 31.418198%),\n        calc(100% - 1.261583%) calc(100% - 26.661607%),\n        calc(100% - 2.456312%) calc(100% - 22.002771%),\n        calc(100% - 4.217852%) calc(100% - 17.530819%),\n        calc(100% - 6.605826%) calc(100% - 13.363987%),\n        calc(100% - 9.657177%) calc(100% - 9.657177%),\n        calc(100% - 13.363987%) calc(100% - 6.605826%),\n        calc(100% - 17.530819%) calc(100% - 4.217852%),\n        calc(100% - 22.002771%) calc(100% - 2.456312%),\n        calc(100% - 26.661607%) calc(100% - 1.261583%),\n        calc(100% - 31.418198%) calc(100% - 0.543282%),\n        calc(100% - 36.21469%) calc(100% - 0.166797%),\n        calc(100% - 41.024763%) calc(100% - 0.022716%),\n        calc(100% - 45.837405%) 100%,\n        45.837405% 100%,\n        41.024763% calc(100% - 0.022716%),\n        36.21469% calc(100% - 0.166797%),\n        31.418198% calc(100% - 0.543282%),\n        26.661607% calc(100% - 1.261583%),\n        22.002771% calc(100% - 2.456312%),\n        17.530819% calc(100% - 4.217852%),\n        13.363987% calc(100% - 6.605826%),\n        9.657177% calc(100% - 9.657177%),\n        6.605826% calc(100% - 13.363987%),\n        4.217852% calc(100% - 17.530819%),\n        2.456312% calc(100% - 22.002771%),\n        1.261583% calc(100% - 26.661607%),\n        0.543282% calc(100% - 31.418198%),\n        0.166797% calc(100% - 36.21469%),\n        0.022716% calc(100% - 41.024763%),\n        0.022716% 41.024763%,\n        0.166797% 36.21469%,\n        0.543282% 31.418198%,\n        1.261583% 26.661607%,\n        2.456312% 22.002771%,\n        4.217852% 17.530819%,\n        6.605826% 13.363987%,\n        9.657177% 9.657177%,\n        13.363987% 6.605826%,\n        17.530819% 4.217852%,\n        22.002771% 2.456312%,\n        26.661607% 1.261583%,\n        31.418198% 0.543282%,\n        36.21469% 0.166797%,\n        41.024763% 0.022716%,\n        45.837405% 0%);\n}\n\n\n\n.cloth-wrp{\n  position: relative;\n  display: inline-block;\n  width:50%;\n  padding: 5px;\n  filter: drop-shadow(5px 5px 5px  rgba(0,0,0,0.5));\n}\n\n.cloth-wrp:before{\n  content:\'\';\n  display:block;\n  position:absolute;\n  top:0;\n  left:0;\n  right:0;\n  bottom:0;\n  width:100%;\n  height:100%;\n  background:#000;\n  clip-path: var(--clip)\n}\n\n.clothoid-corner {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background-color:orange;\n  width:100%;\n  aspect-ratio: 1/1;\n}\n\n.clipped{\n   clip-path: var(--clip)\n}
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

该剪辑路径实际上是多边形近似。

\n

我们需要将方圆包裹在相对定位的父 div 中。
\n此包装器引入了一个使用相同剪辑路径剪辑的伪元素 \xe2\x80\x93。
\n伪元素具有背景颜色,这将导致最终的伪描边颜色。
\n笔划宽度由应用于环绕元素的填充来定义。

\n