5

Creating Gooey Tooltips for Range Sliders using SVG Filters ✨

 1 year ago
source link: https://dev.to/n3r4zzurr0/creating-gooey-tooltips-for-range-sliders-using-svg-filters-54a7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

A few weeks ago, I developed a small library for creating range sliders that can capture a value or a range of values with one or two drag handles.

In this article, I will use it to create range sliders and then create gooey tooltips for them using SVG filters.

GitHub logo n3r4zzurr0 / range-slider-input

A lightweight (~2kB) library to create range sliders that can capture a value or a range of values with one or two drag handles

range-slider-input

A lightweight (~2kB) library to create range sliders that can capture a value or a range of values with one or two drag handles.

Examples / CodePen

Demo

Features

  • High CSS customizability
  • Touch and keyboard accessible
  • Supports negative values
  • Vertical orientation
  • Small and fast
  • Zero dependencies
  • Supported by all major browsers
  • Has a React component wrapper

⚠️It is recommended that you upgrade from v1.x to v2.x! What's new and what's changed in v2.x?


Installation

npm

npm install range-slider-input

Import the rangeSlider constructor and the core CSS:

import rangeSlider from 'range-slider-input';
import 'range-slider-input/dist/style.css';

CDN

<script src="https://cdn.jsdelivr.net/npm/range-slider-input@2/dist/rangeslider.umd.min.js"></script>
<script src="https://unpkg.com/range-slider-input@2"></script>

The core CSS comes bundled with the jsDelivr and unpkg imports.

Usage

import rangeSlider from 'range-slider-input';
import 'range-slider-input/dist/style.css';

const rangeSliderElement = rangeSlider(element);

rangeSlider(element, options =

I have divided this article into 4 sections. Let's begin (or just jump to the final result)!

1. Creating the Range Sliders

Here, I have created two range sliders, one with a single thumb and the other with two thumbs, and have styled them with some custom CSS (refer to the documentation to know more about styling).

So, this is going to be our starting point.

2. Creating the Gooey Effect

Gooey effect can be obtained by applying a blur filter, and by increasing the contrast later, which can be done using the CSS property filter.

filter: blur(6px) contrast(20);

But, there are a few drawbacks to this approach, the worst one being that it falls back to the nearest standard color which restricts the range of the colors that can be used.

This is where the SVG filters are very helpful. For our case, the filter should look like:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <defs>
    <filter id="gooey">
      <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur" />
      <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 15 -6" />
    </filter>
  </defs>
</svg>

Here, the feGaussianBlur filter effect applies the blur filter, and to increase the contrast, we use the feColorMatrix filter effect which involves manipulation of color channels. Here is an extensive article on this filter effect: Finessing feColorMatrix, if you want to dig deeper into this subject.

For our case, we just need to increase the contrast of the alpha channel, so we will leave the RGB channels untouched.

R G B A +
R 1 0 0 0 0
G 0 1 0 0 0
B 0 0 1 0 0
A 0 0 0 15 -6

With this manipulation, the value of the alpha channel gets multiplied by 15 and then (255 * 6) gets subtracted from it, effectively increasing the contrast of the transparency. You can play with these values until you get your desired result.

3. Positioning of blobs over thumbs

After creating the desired gooey effect, we will need to place the blobs over the thumbs of the range sliders.

Create a container element with position set to relative. Inside it, put the slider and the blobs with their position set to absolute and update the left positional property of the blobs whenever the value of the slider is changed. But, what should we update the left property with? Value percentage? No!

Let's see why.

These range sliders are in accordance with <input type="range" />, so the position of the thumbs isn't simply the value percentage. The width of the thumb is taken into account too.

So, in order to calculate the position of a thumb, we will do

const thumbWidth = 30 // 30px for example
const fraction = (value - MIN) / (MAX - MIN)
blobs.style.left = `calc(${fraction * 100}% + ${(0.5 - fraction) * thumbWidth}px)`

where MIN and MAX are the min and max properties of the range slider, which are 0 and 100 by default respectively.

4. Putting everything together

Our final HTML structure looks something like:

<div class="container">
  <div class="slider"></div>
  <div class="blobs">
    <div class="blob"></div>
    <div class="blob"></div>
  </div>
  <div class="value-text"></div>
</div>

Also, to make the code more readable, I have written a wrapper function that creates a range slider, pops up one of the blobs upon user interaction, and updates the position of the blobs whenever there is a change in the value.

function gooeyRangeSlider (element, options = {}, initialIndex = 0) {
  const slider = element.querySelector('.slider')
  const blobs = element.querySelector('.blobs')
  const valueText = element.querySelector('.value-text')

  let value = []

  // initialIndex denotes the index of the thumb over which the blobs have to be placed initially
  const currentIndex = () => {
    // currentValueIndex returns -1 when the thumbs are in idle state
    const index = wrapper.currentValueIndex()
    return index === -1 ? initialIndex : index
  }

  const update = () => {
    const index = currentIndex()
    const fraction = (value[index] - wrapper.min()) / (wrapper.max() - wrapper.min())
    const left = `calc(${fraction * 100}% + ${(0.5 - fraction) * 30}px)`
    blobs.style.left = left
    valueText.style.left = left
    valueText.textContent = value[index]
  }

  const wrapper = rangeSlider(slider, {
    ...options,
    onInput: v => {
      value = v
      update()
    },
    onThumbDragStart: () => {
      blobs.classList.add('active')
      update()
    },
    onThumbDragEnd: () => {
      blobs.classList.remove('active')
    }
  })

  value = wrapper.value()
  update()
}

FINAL RESULT

Thanks 🙂


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK