Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Connecting input range arc thumb to slider with curve

I have a solved question Animating range ticks when moving range thumb. From that question, I have a input type="range" with custom appearance - thumb is made like an arc (half circle), span that takes range's value and is designed as a circled thumb and div with mask that act like ticks - steps.


From this expected result

expected result

I was trying to connect that arc to the slider with a curve. I tried to use pseudo elements but gradient doesn't sync with gradient on the slider and I cannot make a curve like shown in the picture. I also tried using JS canvas to draw that curve and place it on desired place but gradient again doesn't sync - becomes a stationary color.

I thought using a CSS mask but I'm not sure if it's possible to make wanted curve with that.


These are my main research points:

  • CSS Tricks - Styling Cross-Browser Compatible Range Inputs
  • Gradient Stroke Along Curve in Canvas - Tried to make similar curve (connecting point) with canvas

This is my CodePen and the code

// Position of span that shows range value and tick curve position
const tickContainer = document.getElementById('tickContainer');

const range = document.getElementById('range');
const rangeV = document.getElementById('rangeValue');
const setValue = () => {
  // Span position and inner value
  const newValue = Number((range.value - range.min) * 100 / (range.max - range.min));
  const newPosition = 35 - (newValue * 0.7);
  rangeV.style.left = `calc(${newValue}% + (${newPosition}px))`;
  rangeV.innerHTML = `<span>${range.value}%</span>`;
  
  // Tick curve position
  tickContainer.style.setProperty('--p', `calc(${newValue}%)`);
};

// Initialize setValue onload and oninput
document.addEventListener("DOMContentLoaded", setValue);
range.addEventListener('input', setValue);
body {
  font-family: Arial;
  margin: 50px;
}

.range-wrap {
  position: relative;
}

/* Styling of ticks (lines) over the range */
.ticks {
  position: absolute;
  left: -15px;
  right: -15px;
  padding:0 15px;
  top: -30px;
  height: 45px;
  background: repeating-linear-gradient(to right, #D3D3D3 0 1px, transparent 1px 9px);
  background-clip:content-box;
  -webkit-mask: 
    radial-gradient(farthest-side at bottom,transparent 75%, #fff 76% 98%, transparent) 
      var(--p) 0px/100px 50px, 
    linear-gradient(#fff, #fff) var(--p) 100%/95px 10px,
    linear-gradient(#fff, #fff) bottom       /100% 10px;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-composite: source-over,destination-out;
  mask: 
    radial-gradient(farthest-side at bottom,transparent 75%, #fff 76% 98%, transparent) 
      var(--p) 0px/100px 50px, 
    linear-gradient(#fff, #fff) var(--p) 100%/95px 10px,
    linear-gradient(#fff, #fff) bottom       /100% 10px;
  mask-repeat: no-repeat;
  mask-composite: exclude;
}

/* Styling the range */
input[type=range] {
  -webkit-appearance: none;
  appearance: none;
  margin: 20px 0;
  width: 100%;
  height: 4px;
  background-image: linear-gradient(125deg, #e0e0e0 34%, rgb(0,12,110) 100%);
  outline: none;
  transition: all 100ms ease;
}

/* Range track */
input[type=range]::-webkit-slider-runnable-track {
  width: 100%;
  height: 4px;
  cursor: pointer;
  border-radius: 25px;
}

input[type=range]::-moz-range-track {
  width: 100%;
  height: 4px;
  cursor: pointer;
  border-radius: 25px;
}

/* Range thumb */
input[type=range]::-webkit-slider-thumb {
  height: 70px;
  width: 70px;
  -webkit-transform: translateY(-44.3%) rotate(-45deg);
          transform: translateY(-44.3%) rotate(-45deg);
  -webkit-appearance: none;
  appearance: none;
  background: #ddd;
  border: 3px solid transparent;
  border-color: transparent transparent #fff #fff;
  border-radius: 50%;
  cursor: pointer;
  background-image: linear-gradient(white, white), linear-gradient(to right, #e0e0e0 34%, rgb(0,12,110) 100%);
  background-attachment: fixed, fixed;
  background-clip: padding-box, border-box;
  transition: all 200ms ease;
}

input[type=range]::-moz-range-thumb {
  height: 63px;
  width: 63px;
  appearance: none;
  background: #ddd;
  border: 3px solid transparent;
  transition: all 200ms ease;
  border-color: transparent transparent #fff #fff;
  border-radius: 50%;
  cursor: pointer;
  background-image: linear-gradient(white, white), linear-gradient(to right, #e0e0e0 34%, rgb(0,12,110) 100%);
  background-attachment: fixed, fixed;
  background-clip: padding-box, border-box;
}

/* Range value (label) inside of range thumb */
.range-value {
  position: absolute;
  top: -50%;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
      -ms-flex-pack: center;
          justify-content: center;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
  z-index: 99;
  user-select: none;
  select: none;
  pointer-events: none;
}

.range-value span {
  width: 50px;
  height: 50px;
  line-height: 50px;
  text-align: center;
  color: #fff;
  background: rgb(0,12,110);
  font-size: 18px;
  display: block;
  position: absolute;
  top: 20px;
  border-radius: 50%;
  user-select: none;
  select: none;
  pointer-events: none;
  z-index: 100;
}
<div class="range-wrap">
  <!-- Ticks (lines) over slider -->
  <div class="ticks" id="tickContainer">
  </div>
  <!-- Range value inside of range thumb -->
  <div class="range-value" id="rangeValue"></div>
  <!-- Range itself -->
  <input id="range" type="range" min="1" max="100" value="5" step="1">
</div>
like image 282
Vepth Avatar asked Jul 03 '20 22:07

Vepth


People also ask

How do you show the value of the range slider on the thumb of the slider?

You cannot display value in the thumb but you can attach a bubble to the thumb which will contain and display the value as you slide.

What input type should you use if you want to have a slider control?

Definition and Usage The <input type="range"> defines a control for entering a number whose exact value is not important (like a slider control). Default range is 0 to 100. However, you can set restrictions on what numbers are accepted with the attributes below.


Video Answer


1 Answers

Here is different idea where I will rely on mask like my previous answer but this time I will introduce and SVG for the curved part. I will also optimize the code a little to have less of code.

You will notice that I am using the same mask for both the ticks and the range element but with some different values since the ticks need to have a bigger curve.

// Position of span that shows range value and tick curve position
const tickContainer = document.querySelector('.range-wrap');

const range = document.getElementById('range');
const rangeV = document.getElementById('rangeValue');
const setValue = () => {
  // Span position and inner value
  const newValue = Number((range.value - range.min) * 100 / (range.max - range.min));
  const newPosition = 30 - (newValue * 0.6);
  rangeV.style.left = `calc(${newValue}% + (${newPosition}px))`;
  rangeV.innerHTML = `${range.value}%`;
  
  // Tick curve position
  tickContainer.style.setProperty('--p', `calc(${newValue}%)`);
};

// Initialize setValue onload and oninput
document.addEventListener("DOMContentLoaded", setValue);
range.addEventListener('input', setValue);
body {
  font-family: Arial;
  margin: 50px;
}

.range-wrap {
  position: relative;
  --svg:url("data:image/svg+xml;utf8, <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 15 64 50' preserveAspectRatio='none' ><path d='M0 64 C16 64 16 32 32 32 C48 32 48 64 64 64 L64 48 C52 48 52 16 32 16 C12 16 12 48 0 48 L0 64 Z' fill='white'/></svg>") var(--p,0) 0;
}

/* Styling of ticks (lines) over the range */
.ticks {
  --sw:120px; /* control the width of the curve */
  
  position: absolute;
  left: -30px;
  right: -30px;
  padding:0 10px;
  height: 50px;
  background: repeating-linear-gradient(to right, red 0 3px, transparent 1px 9px) content-box;
  -webkit-mask:var(--svg) /var(--sw) 50px,
     linear-gradient(to right, #fff calc(50% - var(--sw)/2 + 1px), transparent 0 calc(50% + var(--sw)/2 - 1px), #fff 0) 
     right var(--p) top 33px/calc(200% - var(--sw)) 16px;
  -webkit-mask-repeat:no-repeat;
  z-index:999;
}

/* Styling the range */
input[type=range] {
  --sw:100px; /* control the width of the curve */
  
  -webkit-appearance: none;
  appearance: none;
  margin: 20px 0 20px -20px;
  padding:0 20px;
  width:100%;
  height: 90px;
  -webkit-mask: 
    var(--svg) /var(--sw) 50px,
    linear-gradient(to right, #fff calc(50% - var(--sw)/2 + 1px), transparent 0 calc(50% + var(--sw)/2  - 1px), #fff 0) 
    right var(--p) top 33px/calc(200% - var(--sw)) 16px;
  -webkit-mask-repeat:no-repeat;
  background: linear-gradient(125deg, #e0e0e0 34%, rgb(0,12,110) 100%);
  outline: none;
}

/* Range track */
input[type=range]::-webkit-slider-runnable-track {
  width: 100%;
  height: 50px;
  cursor: pointer;
  border-radius: 25px;
}

input[type=range]::-moz-range-track {
  width: 100%;
  height: 50px;
  cursor: pointer;
  border-radius: 25px;
}

/* Range thumb */
input[type=range]::-webkit-slider-thumb {
  height: 60px;
  width: 60px;
  -webkit-appearance: none;
  appearance: none;
  border-radius: 50%;
  cursor: pointer;
  opacity:0;
}

input[type=range]::-moz-range-thumb {
  height: 60px;
  width: 60px;
  appearance: none;
  border-radius: 50%;
  cursor: pointer;
  opacity:0;
}

/* Range value (label) inside of range thumb */
.range-value {
  width: 50px;
  height: 50px;
  line-height: 50px;
  text-align: center;
  color: #fff;
  background: rgb(0,12,110);
  font-size: 18px;
  position: absolute;
  transform:translateX(-50%);
  top: 45px;
  border-radius: 50%;
  user-select: none;
  select: none;
  pointer-events: none;
}
<div class="range-wrap">
  <!-- Ticks (lines) over slider -->
  <div class="ticks" id="tickContainer">
  </div>
  <!-- Range value inside of range thumb -->
  <div class="range-value" id="rangeValue"></div>
  <!-- Range itself -->
  <input id="range" type="range" min="1" max="100" value="5" step="1">
</div>

UPDATE

The final version used by the OP:

// Position of span that shows range value and tick curve position
const tickContainer = document.querySelector('.range-wrap');

const range = document.getElementById('range');
const rangeV = document.getElementById('rangeValue');
const setValue = () => {
  // Span position and inner value
  const newValue = Number((range.value - range.min) * 100 / (range.max - range.min));
  const newPosition = 30 - (newValue * 0.6);
  rangeV.style.left = `calc(${newValue}% + (${newPosition}px))`;
  rangeV.innerHTML = `${range.value}%`;
  
  // Tick curve position
  tickContainer.style.setProperty('--p', `calc(${newValue}%)`);
};

// Initialize setValue onload and oninput
document.addEventListener("DOMContentLoaded", setValue);
range.addEventListener('input', setValue);
body {
  font-family: Arial;
  margin: 0;
  min-height: 100vh;
  padding: 50px;
  box-sizing: border-box;
  text-align:center;
}

.range-wrap {
  position: relative;
  --svg:url("data:image/svg+xml;utf8, <svg width='97' height='37' viewBox='0 1.5 97 37' xmlns='http://www.w3.org/2000/svg'><path d='M0 35C14 35 13 2 48.5 2C84 2 80.5 35 97 35' fill='none' stroke='white' stroke-width='4'/></svg>") var(--p,0) 0;
</svg>
}

/* Styling of ticks (lines) over the range */
.ticks {
  --sw:120px; /* control the width of the curve */
  
  position: absolute;
  left: -30px;
  right: -30px;
  top: 0px;
  padding:0 10px;
  height: 50px;
  background: repeating-linear-gradient(to right, #D3D3D3 0 1px, transparent 1px 10px) content-box;
  -webkit-mask:var(--svg) /var(--sw) 50px,
     linear-gradient(to right, #fff calc(50% - var(--sw)/2 + 1px), transparent 0 calc(50% + var(--sw)/2 - 1px), #fff 0) 
     right var(--p) top 38px/calc(200% - var(--sw)) 6px;
  -webkit-mask-repeat:no-repeat;
  z-index:999;
}

/* Styling the range */
input[type=range] {
  --sw:100px; /* control the width of the curve */
  
  -webkit-appearance: none;
  appearance: none;
  margin: 20px 0 20px -20px;
  padding:0 20px;
  width: 100%;
  height: 60px;
  -webkit-mask: 
    var(--svg) /var(--sw) 41px,
    linear-gradient(to right, #fff calc(50% - var(--sw)/2 + 1px), transparent 0 calc(50% + var(--sw)/2  - 1px), #fff 0) 
    right var(--p) top 34.45px/calc(200% - var(--sw)) 4px;
  -webkit-mask-repeat:no-repeat;
  background: linear-gradient(125deg, #e0e0e0 34%, rgb(0,12,110) 100%);
  outline: none;
}

/* Range track */
input[type=range]::-webkit-slider-runnable-track {
  width: 100%;
  height: 50px;
  cursor: pointer;
  border-radius: 25px;
}

input[type=range]::-moz-range-track {
  width: 100%;
  height: 50px;
  cursor: pointer;
  border-radius: 25px;
}

/* Range thumb */
input[type=range]::-webkit-slider-thumb {
  height: 60px;
  width: 60px;
  -webkit-appearance: none;
  appearance: none;
  border-radius: 50%;
  cursor: pointer;
  opacity:0;
}

input[type=range]::-moz-range-thumb {
  height: 60px;
  width: 60px;
  appearance: none;
  border-radius: 50%;
  cursor: pointer;
  opacity:0;
}

/* Range value (label) inside of range thumb */
.range-value {
  width: 55px;
  height: 55px;
  line-height: 60px;
  text-align: center;
  color: #fff;
  background: rgb(0,12,110);
  font-size: 18px;
  position: absolute;
  transform:translateX(-50%);
  top: 32px;
  border-radius: 50%;
  user-select: none;
  select: none;
  pointer-events: none;
}
<h2>Custom range slider with ticks</h2>

<div class="range-wrap">
  <!-- Ticks (lines) over slider -->
  <div class="ticks" id="tickContainer">
  </div>
  <!-- Range value inside of range thumb -->
  <div class="range-value" id="rangeValue"></div>
  <!-- Range itself -->
  <input id="range" type="range" min="1" max="100" value="5" step="1">
</div>
like image 66
Temani Afif Avatar answered Oct 07 '22 13:10

Temani Afif