skip to Main Content

I tried change the coordinates in css, but the level does not stick to the tube and it also breaks the look on image.
i need that gradation was like this:
100 – full, 75 – average, 50 – half, 25 – Low, 0 – Empty

document.getElementsByName('water-level')[0].addEventListener('change', updateWaterLevel);

function updateWaterLevel() {

  var fractionFull = this.value / 100;
  var dy = (1 - fractionFull) * 150;
  document.getElementById("tubeLiquid").setAttribute("transform", "translate(0," + dy +")");
}
.item {
    width: 150px;
    height: 150px;
  }
  
  #tubeLiquid {
    background-color: #74ccf4;
    clip-path: polygon(40% 24%, 38% 28%, 38% 32%, 38% 86%, 38% 91%, 40% 94%, 44% 96%, 57% 96%, 60% 94%, 62% 91%, 62% 86%, 62% 33%, 62% 29%, 60% 25%, 59% 24%);
  }
  
  #bottleMask{
    background-color: #FFFFFF;
    clip-path: polygon(40% 24%, 38% 28%, 38% 32%, 38% 86%, 38% 91%, 40% 94%, 44% 96%, 57% 96%, 60% 94%, 62% 91%, 62% 86%, 62% 33%, 62% 29%, 60% 25%, 59% 24%);
  }
<input type="number" value="" name="water-level" />
<div class="item">
    <svg viewBox="0 0 300 300">
      <defs>
        <mask id="bottleMask" fill="white">
          <path d="M0,0h300v300H0V0z M89,68h122v147H89V68z" />
        </mask>
        <clipPath id="liquidClip">
          <path d="M89,68h122v147H89V68z" />
        </clipPath>
      </defs>
      <image width="300" xlink:href="https://i.ibb.co/HGFQYTj/bottle.png" />
      <path id="tubeLiquid" fill="#74ccf4" d="M0,0h300v300H0V0z" clip-path="url(#liquidClip)" mask="url(#bottleMask)" />
    </svg>
  </div>

2

Answers


  1. As explained in my previous answer, your current clip-path/mask structure is slightly complicated.

    Actually, only the css clip-path has an effect on your graphic.

    Currently you’re just moving your progress/fill bar via translate().
    Your clip-path has relative units and will also move:

    svg{
      overflow:visible;
      width:20em;
      border: 1px solid #ccc;
    }
    
    
    .item {
        width: 150px;
        height: 150px;
      }
      
    
      .clipped{
        clip-path: polygon(40% 24%, 38% 28%, 38% 32%, 38% 86%, 38% 91%, 40% 94%, 44% 96%, 57% 96%, 60% 94%, 62% 91%, 62% 86%, 62% 33%, 62% 29%, 60% 25%, 59% 24%);
      }
        <svg viewBox="0 0 300 300">
            <rect class="clipped" x="0" y="58" width="200" height="233"  fill="#74ccf4" />
            <rect class="clipped" x="115" y="58" width="70" height="233"  fill="#74ccf4" transform="translate(0 50)" />
        </svg>

    CSS clip-path: Add a transparent <rect>

    You can circumvent the aforementioned scaling issue by adding a <rect> filling the entire viewBox:

    let inputWaterLevel = document.querySelector('[name=water-level]');
    let heightLiquid = +tubeLiquid.getAttribute('height');
    let yLiquid = +tubeLiquid.getAttribute('y');
    let liquidPercent = heightLiquid/100;
    
    inputWaterLevel.addEventListener('input', updateWaterLevel);
    
    function updateWaterLevel() {
      let value = +this.value;
      
      // calculate new height
      let h = liquidPercent * value;
      let y = yLiquid + (heightLiquid-h);
      
      // update height and y offset
      tubeLiquid.setAttribute('height', liquidPercent * value )
      tubeLiquid.setAttribute('y', y )
    
    }
    svg{
      overflow:visible;
      width:20em;
    }
    
    
    .item {
        width: 150px;
        height: 150px;
      }
      
    
      .clipped{
        clip-path: polygon(40% 24%, 38% 28%, 38% 32%, 38% 86%, 38% 91%, 40% 94%, 44% 96%, 57% 96%, 60% 94%, 62% 91%, 62% 86%, 62% 33%, 62% 29%, 60% 25%, 59% 24%);
      }
    <input type="number" min="0" max="100" value="50" name="water-level" />
    <div class="item">
      <svg viewBox="0 0 300 300">
        <clipPath id="clip">
          <rect x="118" y="68" width="64" height="228" rx="20" ry="20" />
        </clipPath>
    
        <image width="300" xlink:href="https://i.ibb.co/HGFQYTj/bottle.png" />
        <g class="clipped">
          <rect id="clipBG" x="0" y="0" width="100%" height="100%" fill="transparent" />
          <rect id="tubeLiquid" x="115" y="58" width="70" height="233" fill="#74ccf4" />
        </g>
      </svg>
    </div>

    Native SVG <clipPath>

    let inputWaterLevel = document.querySelector('[name=water-level]');
    let heightLiquid = +tubeLiquid.getAttribute('height');
    let yLiquid = +tubeLiquid.getAttribute('y');
    let liquidPercent = heightLiquid/100;
    
    inputWaterLevel.addEventListener('input', updateWaterLevel);
    
    function updateWaterLevel() {
      let value = +this.value;
      
      // calculate new height
      let height = liquidPercent * value;
      let y = yLiquid + (heightLiquid-height);
      
      // update height and y offset
      tubeLiquid.setAttribute('height', liquidPercent * value )
      tubeLiquid.setAttribute('y', y )
    
    }
    <p><input type="number" min="0" max="100" value="50" name="water-level" /></p>
    <div class="item">
        <svg viewBox="0 0 300 300" width="150">
        <clipPath id="clip">
          <rect x="118" y="68" width="64" height="228" rx="20" ry="20" />
        </clipPath>
          <image width="300" xlink:href="https://i.ibb.co/HGFQYTj/bottle.png" />
            <rect clip-path="url(#clip)" id="tubeLiquid" x="115" y="58" width="70" height="233"  fill="#74ccf4" />
        </svg>
      </div>

    This approach has the advantage, you can draw/position your progress elements as they would appear at 100% progress.
    Now you can change their height relative to 100%:

    let heightLiquid = +tubeLiquid.getAttribute('height');
    let liquidPercent = heightLiquid/100;
    

    The new height of the progress bar would be

      let h = liquidPercent * value;
    

    But we also need to calculate a new y value to align the progress bar to the bottom

    let yLiquid = +tubeLiquid.getAttribute('y');
    

    <line> as progress element

    Using a <line> element for the "tubeLiquid" element can simplify the javaScript function even further as it allows us to set the pathLength attribute to 100.

        waterLevel.addEventListener('input', e => {
          let value = +e.currentTarget.value
          tubeLiquid.setAttribute('stroke-dasharray', `${value} 100`)
        })
    line{
    transition:0.5s
    }
    <p><input type="number" min="0" max="100" value="50" id="waterLevel" name="water-level" /></p>
        <svg viewBox="0 0 300 300" width="150">
            <clipPath id="clip">
                  <rect x="115" y="55" width="69" height="235" rx="20" ry="20" fill="red" />
            </clipPath>
            <image width="300" href="https://i.ibb.co/Mhfwtxp/image-removebg-preview-1.png" />
            <line id="tubeLiquid" clip-path="url(#clip)" pathLength="100" stroke-dasharray="50 100" x1="149.5" y1="290" x2="149.5" y2="50" stroke="#74ccf4" stroke-width="80" />
          </svg>
    Login or Signup to reply.
  2. In the example below, an <input type="range"> determines the percentage values of a linear-gradient.

    Details are commented in example

    // Reference the <input>
    const input = document.getElementById("fill");
    /*
    Bind #fill to the "input" event so that it reacts immediately to
    all user interactions with it.
    */
    input.oninput = fillBottle;
    // Called whenever the user adjusts #fill 
    function fillBottle(e) {
      // Reference the <html>
      const root = document.documentElement;
      // Reference <output id="percentage">
      const perc = document.getElementById("percentage");
      // Get the value of #fill and then change it from a string to a number
      let base = parseInt(this.value);
      // Apply this formula to derive a inverse percent number
      let mod = 100 - base + "%";
      // Display the percentage with <output id="percentage">
      let view = base + "%";
      // Pad the value of #percentage with 6 zeros
      perc.value = view.padStart(4, "0");
      /* 
      Change the value of --empty on :root -- this will change the
      percentage values in .water styles
      */
       root.style.cssText = `--empty: ${mod}`;
    }
    :root {
      --empty: 50%/* Initial value of <html> variable -- the value
      of the variable applies anywhere in the styles as var(--empty)  */
      ;
      font: 2ch/1.15 Consolas;
    }
    
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      background: white;
      overflow-y: scroll;
    }
    
    
    /*
    This <div> sits beneath everything -- it's background-image is a
    gradient of white and blue. it's only visible through the trans-
    parent part of .bottle.
    */
    
    .water {
      position: relative;
      top: 60px;
      z-index: -1;
      width: 103px;
      height: 365px;
      background-image: linear-gradient(white calc(var(--empty)), #46CBFF calc(var(--empty)), white 95%)/* This background's perc-
      centages are equal to --empty which is driven by the input of
      <input id="fill"> */
      ;
      background-repeat: no-repeat;
      background-size: cover;
    }
    
    .bottle {
      position: absolute;
      left: 0;
      bottom: 5%;
      z-index: 1;
      width: 104px;
      height: 360px;
      padding-bottom: 10px;
      background=color: white;
      background-image: url(https://i.ibb.co/SBRwQTQ/bottle-mask.png)/* This image is transparent within the bottle shape
      border and opaque elsewhere. The image sits on top of evrery-
      thing */
      ;
      background-repeat: no-repeat;
      background-size: cover;
    }
    
    #fill {
      display: block;
      width: 200px;
      height: 10px;
      transform: rotate(-90deg);
    }
    
    #percentage {
      font-size: 3rem
    }
    <div class="water">
      <div class="bottle"></div>
    </div>
    
    <input id="fill" type="range" min="0" max="100" value="50">
    <output id="percentage">050%</output>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search