Expanding a feature that modifies the CSS properties of every input element

Creating a text tracking (letter-spacing) demonstration where range sliders are used to update CSS properties. I'm looking to improve the functionality so that the labels also update dynamically, without repeating

output2.textContent = this.value;
. Any elegant solutions?

Also, experiencing UI issues when values transition from (e.g.,) 0.09 -> 0.1 -> 0.11. Is there a way to always display two decimal places? The script is included below.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Trax…text tracking tool</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Chivo&family=Chivo+Mono&family=Playfair+Display+SC:wght@700&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
    <meta name="description" content="description">
    <style>
        * {
            outline: cornflowerblue dotted 0.5px;
        }

        * {
            margin: 0;
        }

        :root {
            --_trax-fontFamily-caps: 'Playfair Display', serif;
            --_trax-fontFamily-scaps: 'Playfair Display SC', serif;
            --_trax-fontFamily-body: 'Chivo', sans-serif;
            --_trax-fontFamily-mono: 'Chivo Mono', monospace;

            --_trax-textTracking-caps: var(--trax-textTracking-caps, 0.00cap);
            --_trax-textTracking-scaps: var(--trax-textTracking-scaps, 0.00ex);
            --_trax-textTracking-body: var(--trax-textTracking-body, 0.00em);
            --_trax-textTracking-mono: var(--trax-textTracking-mono, 0.00ch);

            --_trax-text-measure: var(--trax-text-measure, 66em);
        }

        body {
            box-sizing: content-box;
            margin-inline: auto;
            text-align: center;
            max-inline-size: var(--_trax-text-measure);
            padding-inline-start: 1rem;
            padding-inline-end: 1rem;
            display: flex;
            flex-direction: column;
            align-items: center;
            font-family: var(--_trax-fontFamily-mono);
        }

        h1 {
            font-size: 5rem;
        }

        h1.caps {
            font-family: var(--_trax-fontFamily-caps);
            letter-spacing: var(--_trax-textTracking-caps);
            text-transform: uppercase;
        }

        h1.scaps {
            font-family: var(--_trax-fontFamily-scaps);
            letter-spacing: var(--_trax-textTracking-scaps);
        }

        .align-self\:flex-start,
        .asfs {
            align-self: flex-start;
        }

        /* add range slider code */
    </style>
</head>

<body>

    <p class="align-self:flex-start">Capitals</p>
    <h1 class="caps">Hamburgevons</h1>
    <div class="controls">
        <label for="trax-textTracking-caps" id="value-caps">0.00</label><span>&#8198;cap</span>
        <input name="trax-textTracking-caps" type="range" min="-0.30" max="0.30" value="0.00" step="0.01" data-uom="cap">
        <button>CSS</button>
    </div>

    <p class="align-self:flex-start">Small Capitals</p>
    <h1 class="scaps">Hamburgevons</h1>
    <div class="controls">
        <label for="trax-textTracking-scaps" id="value-scaps">0.00</label><span>&#8198;ex</span>
        <input name="trax-textTracking-scaps" type="range" min="-0.30" max="0.30" value="0.00" step="0.01" data-uom="ex">
        <button>CSS</button>
    </div>

    <script>
        const rangeSliders = document.querySelectorAll('input');
        //const labels = document.querySelectorAll('label');
        const output1 = document.querySelector('#value-caps');
        const output2 = document.querySelector('#value-scaps');

        function updateProp(event) {
            const uom = this.dataset.uom || '';
            document.documentElement.style.setProperty(`--_${this.name}`, this.value + uom);
            output1.textContent = this.value;
            output2.textContent = this.value;
        }

        rangeSliders.forEach(input => {
            input.addEventListener('input', updateProp);
        });
    </script>

</body>

</html>

Answer №1

In my opinion, the repetition in this particular case adds to the overall elegance. It would be wise to keep it as it is for the sake of clarity rather than attempting to craft a clever solution that achieves the same outcome.

Regarding the inclusion of 2 decimal points, the simplest approach would be to use the following code if you are fine with the text content being a number:

output1.textContent = parseFloat(this.value).toFixed(2);
output2.textContent = parseFloat(this.value).toFixed(2);

Update:

Addressing the feedback provided below, another approach to consider, especially when more sliders and labels are introduced, is as follows:

        const rangeSliders = document.querySelectorAll('input[type="range"]');
        const labels = document.querySelectorAll('label');

        function updateProp(event) {
            const uom = this.dataset.uom || '';
            document.documentElement.style.setProperty(`--_${this.name}`, this.value + uom);

            const label = labels[Array.from(rangeSliders).indexOf(this)];
            label.textContent = parseFloat(this.value).toFixed(2);
        }

        rangeSliders.forEach((input, index) => {
            input.addEventListener('input', updateProp);
            labels[index].textContent = parseFloat(input.value).toFixed(2);
        });

This method ensures that only the correct label is updated based on the index of the current slider, while also fixing the floating point precision for you.

Answer №2

If you want to update only the specific <label> element related to the <input> element that is being updated, you can achieve this by utilizing DOM tree querying starting from the <input> element itself (referred to as this or event.target in the updateProp() function). For instance, both labels are the 2nd-previous siblings of the input element, so you can access them "relatively" with the following code:

this.previousElementSibling.previousElementSibling.textContent = this.value

To prevent any "UI jank" when transitioning between 0.09 ↔ 0.1 ↔ 0.11 and so on, you can consider using the .padEnd() method on strings to maintain the decimal places, as shown below:

const padTargetLength = this.value.startsWith("-") ? 5 : 4;
this.previousElementSibling.previousElementSibling.textContent =
  this.value.padEnd(padTargetLength, "0");

const rangeSliders = document.querySelectorAll("input");
//const labels = document.querySelectorAll('label');
const output1 = document.querySelector("#value-caps");
const output2 = document.querySelector("#value-scaps");

function updateProp(event) {
  const uom = this.dataset.uom || "";
  document.documentElement.style.setProperty(
    `--_${this.name}`,
    this.value + uom,
  );

  const padTargetLength = this.value.startsWith("-") ? 5 : 4;
  this.previousElementSibling.previousElementSibling.textContent =
    this.value.padEnd(padTargetLength, "0");
}

rangeSliders.forEach((input) => {
  input.addEventListener("input", updateProp);
});
* {
  outline: cornflowerblue dotted 0.5px;
}

* {
  margin: 0;
}

 :root {
  --_trax-fontFamily-caps: 'Playfair Display', serif;
  --_trax-fontFamily-scaps: 'Playfair Display SC', serif;
  --_trax-fontFamily-body: 'Chivo', sans-serif;
  --_trax-fontFamily-mono: 'Chivo Mono', monospace;
  --_trax-textTracking-caps: var(--trax-textTracking-caps, 0.00cap);
  --_trax-textTracking-scaps: var(--trax-textTracking-scaps, 0.00ex);
  --_trax-textTracking-body: var(--trax-textTracking-body, 0.00em);
  --_trax-textTracking-mono: var(--trax-textTracking-mono, 0.00ch);
  --_trax-text-measure: var(--trax-text-measure, 66em);
}

body {
  box-sizing: content-box;
  margin-inline: auto;
  text-align: center;
  max-inline-size: var(--_trax-text-measure);
  padding-inline-start: 1rem;
  padding-inline-end: 1rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: var(--_trax-fontFamily-mono);
}

h1 {
  font-size: 5rem;
}

h1.caps {
  font-family: var(--_trax-fontFamily-caps);
  letter-spacing: var(--_trax-textTracking-caps);
  text-transform: uppercase;
}

h1.scaps {
  font-family: var(--_trax-fontFamily-scaps);
  letter-spacing: var(--_trax-textTracking-scaps);
}

.align-self\:flex-start,
.asfs {
  align-self: flex-start;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Trax…text tracking tool</title>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Chivo&family=Chivo+Mono&family=Playfair+Display+SC:wght@700&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
  <meta name="description" content="description">
</head>

<body>

  <p class="align-self:flex-start">Capitals</p>
  <h1 class="caps">Hamburgevons</h1>
  <div class="controls">
    <label for="trax-textTracking-caps" id="value-caps">0.00</label><span>&#8198;cap</span>
    <input name="trax-textTracking-caps" type="range" min="-0.30" max="0.30" value="0.00" step="0.01" data-uom="cap">
    <button>CSS</button>
  </div>

  <p class="align-self:flex-start">Small Capitals</p>
  <h1 class="scaps">Hamburgevons</h1>
  <div class="controls">
    <label for="trax-textTracking-scaps" id="value-scaps">0.00</label><span>&#8198;ex</span>
    <input name="trax-textTracking-scaps" type="range" min="-0.30" max="0.30" value="0.00" step="0.01" data-uom="ex">
    <button>CSS</button>
  </div>
</body>
</html>

Answer №3

To achieve elegance, a simple solution is to consolidate the code into one line, such as output1 = output2 = this.value, which effectively accomplishes the task. For handling decimals, I implemented a series of conditional statements to facilitate the conversion process.

<!DOCTYPE html>
<html lang="en>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Trax...text tracking tool</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Chivo&family=Chivo+Mono&family=Playfair+Display+SC:wght@700&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
    <meta name="description" content="description">
    <style>
        * {
            outline: cornflowerblue dotted 0.5px;
        }

        * {
            margin: 0;
        }

        :root {
            --_trax-fontFamily-caps: 'Playfair Display', serif;
            --_trax-fontFamily-scaps: 'Playfair Display SC', serif;
            --_trax-fontFamily-body: 'Chivo', sans-serif;
            --_trax-fontFamily-mono: 'Chivo Mono', monospace;

            --_trax-textTracking-caps: var(--trax-textTracking-caps, 0.00cap);
            --_trax-textTracking-scaps: var(--trax-textTracking-scaps, 0.00ex);
            --_trax-textTracking-body: var(--trax-textTracking-body, 0.00em);
            --_trax-textTracking-mono: var(--trax-textTracking-mono, 0.00ch);

            --_trax-text-measure: var(--trax-text-measure, 66em);
        }

        body {
            box-sizing: content-box;
            margin-inline: auto;
            text-align: center;
            max-inline-size: var(--_trax-text-measure);
            padding-inline-start: 1rem;
            padding-inline-end: 1rem;
            display: flex;
            flex-direction: column;
            align-items: center;
            font-family: var(--_trax-fontFamily-mono);
        }

        h1 {
            font-size: 5rem;
        }

        h1.caps {
            font-family: var(--_trax-fontFamily-caps);
            letter-spacing: var(--_trax-textTracking-caps);
            text-transform: uppercase;
        }

        h1.scaps {
            font-family: var(--_trax-fontFamily-scaps);
            letter-spacing: var(--_trax-textTracking-scaps);
        }

        .align-self\:flex-start,
        .asfs {
            align-self: flex-start;
        }

        /* add range slider code */
    </style>
</head>

<body>

    <p class="align-self:flex-start">Capitals</p>
    <h1 class="caps">Hamburgevons</h1>
    <div class="controls">
        <label for="trax-textTracking-caps" id="value-caps">0.00</label><span>&#8198;cap</span>
        <input name="trax-textTracking-caps" type="range" min="-0.30" max="0.30" value="0.00" step="0.01" data-uom="cap">
        <button>CSS</button>
    </div>

    <p class="align-self:flex-start">Small Capitals</p>
    <h1 class="scaps">Hamburgevons</h1>
    <div class="controls">
        <label for="trax-textTracking-scaps" id="value-scaps">0.00</label><span>&#8198;ex</span>
        <input name="trax-textTracking-scaps" type="range" min="-0.30" max="0.30" value="0.00" step="0.01" data-uom="ex">
        <button>CSS</button>
    </div>

    <script>
        const rangeSliders = document.querySelectorAll('input');
        //const labels = document.querySelectorAll('label');
        const output1 = document.querySelector('#value-caps');
        const output2 = document.querySelector('#value-scaps');

        function updateProp(event) {
            const uom = this.dataset.uom || '';
            document.documentElement.style.setProperty(`--_${this.name}`, this.value + uom);
            let value = this.value;
            if (Math.abs(value).toString().length < 4){
                value += "0";
                if (value == "00"){
                  value = "0.00";
                }
            }
            
            output1.textContent = output2.textContent = value;
        }

        rangeSliders.forEach(input => {
            input.addEventListener('input', updateProp);
        });
    </script>

</body>

</html>

Answer №4

Here is one method, accompanied by detailed comments within the code:

// Some utility functions for convenience and efficiency
const D = document,
  get = (selector, context = D) => context.querySelector(selector),
  getAll = (selector, context = D) => [...context.querySelectorAll(selector)],
  roundTo = (n, d = 0) => Math.round(parseFloat(n) * Math.pow(10, d)) / Math.pow(10, d);

// Grab all <input> elements with type="range":
const sliders = getAll('input[type=range]');

// Define the update function to handle changes, taking an Event Object as parameter
const update = (evt) => {
  // Destructuring assignment to extract properties from the Event Object
  let {
    currentTarget: changed,
    isTrusted,
  } = evt,
  {
    labels,
    value
  } = changed,
  wrapper = changed.closest('div'),
    valueAsNumber = parseFloat(value),
    uom = changed.dataset.uom,
    isNegative = valueAsNumber < 0;

  if (false === isTrusted) {
    labels.forEach(
      (lbl) => {
        let sibling = lbl.nextElementSibling;
        if (sibling.matches('span')) {
          sibling.textContent = `\u2006${uom}`;
        }
      });
  }

  labels.forEach(
    (lbl) => {
      lbl.classList.toggle('numberIsNegative', isNegative);
      lbl.textContent = roundTo(Math.abs(valueAsNumber), 2).toFixed(2);
      get(':root').style.setProperty(`--${changed.name}`, valueAsNumber + uom)
    });
}

const fakeInputEvent = new Event('input');

sliders.forEach(
  (el) => {
    el.addEventListener('input', update);
    el.dispatchEvent(new Event('input'));
  });
* {
  box-sizing: border-box;
  margin: 0;
  outline: cornflowerblue dotted 0.5px;
  padding: 0;
}

:root {
  --_trax-fontFamily-caps: 'Playfair Display', serif;
  ...
}

body {
  box-sizing: content-box;
  ...
}

h2 {
  ...
}

.caps {
  ...
}

.scaps {
  ...
}

.align-self\:flex-start,
...

label {
  position: relative;
}

label::before {
  ...
}

label.numberIsNegative::before {
  ...
}
<link rel="preconnect" href="https://fonts.googleapis.com">
...

<p>Capitals</p>
...
<h2 class="caps">Hamburgevons</h2>
<div class="controls">
  <label for="trax-textTracking-caps" id="value-caps">0.00</label>
  <span></span>
  <input id="trax-textTracking-caps" name="trax-textTracking-caps" type="range" min="-0.30" max="0.30" value="0.00" step="0.01" data-uom="cap">
  <button>CSS</button>
</div>

<p>Small Capitals</p>
...
<h2 class="scaps">Hamburgevons</h2>
<div class="controls">
  <label for="trax-textTracking-scaps" id="value-scaps">0.00</label>
  <span></span>
  <input id="trax-textTracking-scaps" name="trax-textTracking-scaps" type="range" min="-0.30" max="0.30" value="0.00" step="0.01" data-uom="ex">
  <button>CSS</button>
</div>

Link to JS Fiddle demo.

References:

Bibliography:

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Add to MongoDB only if the entry does not already exist; otherwise, move on

Can I perform a conditional insert in MongoDB? //Pseudo code Bulk Insert Item : If Key exists Skip, don't throw error If key does not exist Add item If I do single inserts, it might return an error or insert into the collection. But is thi ...

Tips for adjusting the header color in materialize framework?

I've been working on a web template and I'm looking to customize the color displayed in the Android browser (maybe using JS?). The default color is blue. How can I go about changing it? https://i.sstatic.net/RxLbS.jpg Appreciate any help! ...

CSS3 animation for rotating elements

If I have this box in CSS3 (also if, for what I understand, this is not CSS3, just browsers specific) : HTML <div id="container"> <div id="box">&nbsp;</div> </div> ​ CSS Code #container { paddin ...

Using three.js to input text instead of particles within a particle cloud

I have a unique three.js codepen project where square particles drift through the space. However, I am now looking to enhance it by incorporating text (perhaps using geometry?) instead of the square particles, creating a word/tag cloud effect. Is this eve ...

Resizing columns in HTML table remains effective even after the page is refreshed

I've created HTML pages with tables that have multiple columns, but I'm experiencing an issue. The columns are not resizable until I refresh the page. Could someone assist me in fixing this problem and explaining why it's happening? ...

The issue with the dispatch function not working in the Component props of React Redux

I'm struggling with my colorcontrol issue. I've been attempting to use this.props.dispatch(triggerFBEvent(fbID, method, params)) without success. Interestingly, it seems to work fine if I just use triggerFBEvent(fbID, method, params). However, I ...

Switch the text display by clicking on a different button in the list

I am currently dealing with an issue involving a list of boxes containing text/images and buttons for expanding/collapsing the text. Whenever I click on another item's button, the text box that was previously opened gets closed, but the button text re ...

Issues regarding innerHTML

I have a collapsed table with a link that collapses its content. My goal is to change the link (such as from "+" to "-" and vice versa) using JavaScript. I was able to achieve this, but now my table no longer collapses. Here is the code snippet: <div c ...

When using a callback function to update the state in React, the child component is not refreshing with the most recent properties

Lately, I've come across a peculiar issue involving the React state setter and component re-rendering. In my parent component, I have an object whose value I update using an input field. I then pass this updated state to a child component to display t ...

Changing CSS attributes with jQuery when conditions are met

After creating menus with expandable items that change style upon clicking, I encountered an issue where the styling changes were not being applied correctly. To address this problem, I followed Sushanth's suggestion and replaced $this with $(this), ...

unable to decrease the gap between the rows of the table

I'm currently developing an application that involves a table with rows and columns, along with a checkbox container arranged as shown in the image below: https://i.stack.imgur.com/QoVrg.png One issue I'm facing is getting the letters A, B, C, ...

Is there a way to calculate the height of a component that is only rendered conditionally?

I have a scenario where I need to dynamically adjust the height of a component that is conditionally rendered without explicitly setting it. The height should automatically match the size of its content. To achieve this, I am using a reference to determin ...

Tips for deploying a Nuxt application using an alternative command line interface

Despite my best efforts scouring the internet for a solution, I have yet to find a method to resolve my issue. The problem lies with my nuxt.js SPA being blocked by my company firewall when accessed by other users at the designated host/port. While servin ...

What are the steps to customize the appearance of a ComboBox using CSS?

I'm struggling to style the items in a combo box when it's dropped down. Currently using vaadin-time-picker on Svelte, but my issue persists due to the combo box included. Despite experimenting with CSS, I haven't had any success. My goal i ...

Is it possible to exclusively target a child div using JavaScript in CSS, without affecting the parent div?

I'm in the process of developing a calendar feature where users can select a specific "day" element to access a dropdown menu for time selection. The functionality is working fine, but I've encountered an issue. When a user selects a time from th ...

Error: Unable to access the 'version' property of null

Having trouble installing any software on my computer, I've attempted various solutions suggested here but none have been successful. $ npm install axios npm ERR! Cannot read property '**version**' of null npm ERR! A complete log of this ru ...

Can someone guide me on how to personalize a marker icon in Quasar while utilizing Vue2-Leaflet for mapping?

I'm facing an issue with displaying an icon marker image in my Vue2-Leaflet and Quasar project. Instead of the desired image, I am seeing a broken image icon and encountering a 404 error in the console. Despite researching various solutions, I was abl ...

After moving a JavaScript application to heroku, the script appears to be nonfunctional

I developed a basic javascript application using mojs, an animation library, and aimed to host it on heroku. To begin with, I attempted the "heroku create" command to deploy the original app on heroku - although the app was accessible, the script did not f ...

tag directive not functioning properly

I have encountered an issue while trying to create a simple custom directive. When I specify the directive as <div my-info></div> in my HTML file, it works fine. However, when I specify it as <my-info></my-info>, it does not work. ...

CSS directives are not compatible with Internet Explorer 8

I implemented a registration form with CSS rules that highlight error fields with a red border when users submit incorrect or empty data. However, this functionality is not working in Internet Explorer. The red border appears correctly in Firefox and Safar ...