Home > Back-end >  How to make a circular bullet point containing a number where the number text must scale
How to make a circular bullet point containing a number where the number text must scale

Time:03-05

So I have been asked to make a section number that is constructed as a non-filled circle with a 2px border containing a number. The range of the numbers is 1 to 999.

As you can see from the example below, it looks pleasing at one & two digit section numbers, but when we hit three digits the number is clipped.

enter image description here

My thinking is that there needs to be a process where the text is drawn, measured, then scaled to fit into the target space inside the circle, where the target space is effectively a square rect 60% of the diameter of the circle.

However, no JS is allowed in the solution.

I thought it might be possible using SVG and its scaling capabilities via the 'preserveAspectRatio' parameter. However the image above is a screen grab of my SVG results. Working snippet below.

My intention with the code was to have the inner SVG containing the text resize proportionally so that it would fit the width of the parent, with the height set to auto so that it would change in proportion.

Can anyone tell me where I am going wrong?

PS. I am using Chrome on PC to test.

Note: In the following snippet the output looks like the image, so 8, then 88, then 888. The markup is the same for each case - only the text content changes.

body {
  padding: 20px;
  overflow: hidden;
  background-color: #f0f0f0;
}


.counterDiv {
  width: 44px;
  max-width: 44px; 
  min-width: 44px;
  height: 44px;
  max-height: 44px; 
  min-height: 44px;  
  margin: 0;
  padding: 0;
}

svg {
  width: 100%;
  height: 100%;            
}

text {
  font: normal normal 18pt Helvetica, Arial, Verdana;
}
<div class='counterDiv' style='position: relative;'>
  <svg viewbox="0 0 44 44">
    <g>
      <circle cx="22" cy="22" r="20" stroke="#cd1041" stroke-width="2px" fill-opacity="0" />     
      <svg viewBox="0 0 100 auto" x="20%" width="60%"   preserveAspectRatio="xMidYMid meet">
        <text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle"  fill="#cd1041" dy=".1em">8</text>
      </svg>
    </g>
  </svg>
</div>

<div class='counterDiv' style='position: relative;'>
  <svg viewbox="0 0 44 44">
    <g>
      <circle cx="22" cy="22" r="20" stroke="#cd1041" stroke-width="2px" fill-opacity="0" />     
      <svg viewBox="0 0 100 auto" x="20%" width="60%"   preserveAspectRatio="xMidYMid meet">
        <text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle"  fill="#cd1041" dy=".1em">88</text>      
      </svg>
    </g>
  </svg>
</div>

<div class='counterDiv' style='position: relative;'>
  <svg viewbox="0 0 44 44">
    <g>
      <circle cx="22" cy="22" r="20" stroke="#cd1041" stroke-width="2px" fill-opacity="0" />     
      <svg viewBox="0 0 100 auto" x="20%" width="60%"   preserveAspectRatio="xMidYMid meet">
        <text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle"  fill="#cd1041" dy=".1em">888</text>      
      </svg>
    </g>
  </svg>
</div>

CodePudding user response:

A posible solution would be using textLength and lengthAdjust. The lengthAdjust attribute controls how the text is stretched into the length defined by the textLength attribute. One inconvinient would be that the 1 digit numbers would be stretched.

An alternative solution would be using a smaller font size.

Also you may want to use javascript to target only the 3 digit text elements.

body {
  padding: 20px;
  overflow: hidden;
  background-color: #f0f0f0;
}


.counterDiv {
  width: 44px;
  max-width: 44px; 
  min-width: 44px;
  height: 44px;
  max-height: 44px; 
  min-height: 44px;  
  margin: 0;
  padding: 0;
}

svg {
  width: 100%;
  height: 100%;            
}

text {
  font: normal normal 18pt Helvetica, Arial, Verdana;
}
<div class='counterDiv' style='position: relative;'>
  <svg viewbox="0 0 44 44">
    <g>
      <circle cx="22" cy="22" r="20" stroke="#cd1041" stroke-width="2px" fill-opacity="0" />     
      <svg viewBox="0 0 100 auto" x="20%" width="60%"   preserveAspectRatio="xMidYMid meet">
        <text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle"  fill="#cd1041" dy=".1em" textLength="25" lengthAdjust="spacingAndGlyphs">8</text>
      </svg>
    </g>
  </svg>
</div>

<div class='counterDiv' style='position: relative;'>
  <svg viewbox="0 0 44 44">
    <g>
      <circle cx="22" cy="22" r="20" stroke="#cd1041" stroke-width="2px" fill-opacity="0" />     
      <svg viewBox="0 0 100 auto" x="20%" width="60%"   preserveAspectRatio="xMidYMid meet">
        <text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle"  fill="#cd1041" dy=".1em" textLength="25" lengthAdjust="spacingAndGlyphs">88</text>      
      </svg>
    </g>
  </svg>
</div>

<div class='counterDiv' style='position: relative;'>
  <svg viewbox="0 0 44 44">
    <g>
      <circle cx="22" cy="22" r="20" stroke="#cd1041" stroke-width="2px" fill-opacity="0" />     
      <svg viewBox="0 0 100 auto" x="20%" width="60%"   preserveAspectRatio="xMidYMid meet">
        <text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle"  fill="#cd1041" dy=".1em" textLength="25" lengthAdjust="spacingAndGlyphs">888</text>      
      </svg>
    </g>
  </svg>
</div>

CodePudding user response:

Not for OP,
but for all those SVG loving people who have an inner-guide (or sensible boss)
that tells them: "JavaScript is fine, when applied with common sense"

customElements.define("svg-counter", class extends HTMLElement {
  connectedCallback() {
    this.render();
  }
  render(
    val = this.getAttribute("value") || "888",
    color = "green",
    circlestrokewidth =  4,
    circlestroke = "red",
    circlefill = "none"
  ){
    let id = "P"   (new Date() / 1); // uniqueid
    let singleDigit = val.length == 1;
    this.innerHTML = `
    <svg viewbox="0 0 100 100">
      <circle cx="50" cy="50" r="${50-circlestrokewidth}" stroke="${circlestroke}"
              fill="${circlefill}" stroke-width="${circlestrokewidth}"/>     
      <path id="${id}" pathLength="100" d="M0 60H100" stroke="none"/>
      <text transform="scale(${singleDigit?1:.7})" transform-origin="50 50">
        <textPath href="#${id}" method="stretch" 
                  textlength="100" lengthAdjust="${singleDigit?"":"spacingAndGlyphs"}"
                  startoffset="50" text-anchor="middle" dominant-baseline="middle"
                  font-family="Helvetica"
                  fill="${color}" font-size="${100}px">${val}</textPath>
      </text>
    </svg>`;
  }
});
svg {
  width: 20%;
}
<svg-counter value="8"></svg-counter>
<svg-counter value="88"></svg-counter>
<svg-counter value="888"></svg-counter>
<svg-counter value="8888"></svg-counter>

Alas FireFox has some issues, slight tweaked version to make it work in FireFox:

Alas I have a boss who says "We don't care about FireFox customers"

customElements.define("svg-counter", class extends HTMLElement {
  connectedCallback() {
    this.render();
  }
  render(
    val = this.getAttribute("value") || "888",
    color = "green",
    circlestrokewidth =  4,
    circlestroke = "red",
    circlefill = "none"
  ){
    let id = "P"   (new Date() / 1)   val.length; // uniqueid
    let singleDigit = val.length == 1;
    this.innerHTML = `
    <svg viewbox="0 0 100 100">
      <circle cx="50" cy="50" r="${50-circlestrokewidth}" stroke="${circlestroke}"
              fill="${circlefill}" stroke-width="${circlestrokewidth}"/>     
      <path id="${id}" pathLength="100" d="M0 55H100" stroke="blue"/>
      <text transform="scale(${[0,1.7,1.2,.9,.7][val.length]})" transform-origin="50 50">
        <textPath href="#${id}" method="stretch" 
                  textlength="100" lengthAdjust="${singleDigit?"":"spacingAndGlyphs"}"
                  startoffset="50" text-anchor="middle" dominant-baseline="middle"
                  font-family="Helvetica"
                  fill="${color}" font-size="50px">${val}</textPath>
      </text>
    </svg>`;
  }
});
svg {
  width: 20%;
}
<svg-counter value="8"></svg-counter>
<svg-counter value="88"></svg-counter>
<svg-counter value="888"></svg-counter>
<svg-counter value="8888"></svg-counter>

  •  Tags:  
  • svg
  • Related