React: Setting a dual color slider thumb to an image with transparent background

Viewed 551

I made a slider that is supposed to be blue on the left and gray on the right. The blue is also supposed to be thicker than the gray.

enter image description here

I have 2 problems with it.

  1. If the thumb piece has a transparent cutout, then the blue part of the slider doesn't extend all the way to the left side of what is visible on the image.
  2. I would like the edges of the slider to be round, especially the blue edge(I might not need the gray)

By round edges I mean like this enter image description here


Here is a codesandbox of the code

And the code itself

import React from "react";
import styled from "styled-components";
const image = "https://i.imgur.com/XMfYtkv.png";
const thumb = {
  width: 25,
  height: 26
};

const height = 36;
const trackHeight = 4;

// colours
const lowerBackground = `linear-gradient(to bottom, gray, gray) 100% 50% / 100% ${
  trackHeight / 2.5
}px no-repeat transparent`;
const upperBackground = `linear-gradient(to bottom, blue, blue) 100% 50% / 100% ${
  trackHeight / 2.5
}px no-repeat transparent`;

const makeLongShadow = (color, size) => {
  // Colors the slider to the right of the thumbpiece
  let i = 1;
  let shadow = `-${i}px 0 0 ${size} ${color}`;

  for (; i < 1950; i++) {
    //If i is a small number, like 720, then when the slider gets set to its minimum, the end of the slider is the color for the left side of the slider, when it should be the color for the right side
    shadow = `${shadow}, -${i}px 0 0 ${size} ${color}`;
  }

  return shadow;
};

const StyledSlider = styled.input`
  overflow: hidden;
  appearance: none;
  height: ${height}px;
  cursor: pointer;

  marginbottom: 0;

  &:focus {
    outline: none;
  }

  &::-webkit-slider-runnable-track {
    width: 100%;
    height: ${height}px;
    background: ${lowerBackground};
  }

  &::-webkit-slider-thumb {
    position: relative;
    appearance: none;
    height: ${thumb.height}px;
    width: ${thumb.width}px;
    background: url(${image});
    background-size: cover;
    border: 0;
    top: 50%;
    transform: translateY(-50%);
    box-shadow: ${makeLongShadow("blue", "-11px")};
    transition: background-color 150ms;
  }

  &::-moz-range-progress {
    background: ${lowerBackground};
  }

  &::-moz-range-track,
  &::-moz-range-progress {
    width: 100%;
    height: ${height};
    background: ${upperBackground};
  }

  &::-moz-range-thumb {
    appearance: none;
    margin: 0;
    height: ${thumb.height}px;
    width: ${thumb.height}px;
    background: url(${image});
    border: 0;
    transition: background-color 150ms;
  }

  &::-ms-track {
    width: 100%;
    height: ${height}px;
    border: 0;
    /* color needed to hide track marks */
    color: transparent;
    background: transparent;
  }

  &::-ms-fill-lower {
    background: ${lowerBackground};
  }

  &::-ms-fill-upper {
    background: ${upperBackground};
  }

  &::-ms-thumb {
    appearance: none;
    height: ${thumb.height}px;
    width: ${thumb.height}px;
    background: url(${image});
    border-radius: 100%;
    border: 0;
    transition: background-color 150ms;
    /* IE Edge thinks it can support -webkit prefixes */
    top: 0;
    margin: 0;
    box-shadow: none;
  }
`;

//!Black line is too thick when then margin at the bottom is 11 or 10 ? I don't know what would cause this
export default (props) => <StyledSlider type="range" {...props} />;

1 Answers

I noticed box-shadow repetition is causing slow performance, I fixed your code problems and improved the code. In the code, I wrote comments that will help you to understand what I did.

import React from "react";
import ReactDOM from "react-dom";
import styled, { css } from "styled-components";

import "./styles.css";
const image = "https://i.imgur.com/XMfYtkv.png";

const thumbD = "50px"; // image width + height prefered same value

const filllC = "blue"; //first line
const trackC = "gray"; //second line
const trackH = "0.6em"; //dont change it if not neccessry
const firstFillthicker = 10; //in px
const secondFillthicker = 4; //in px

const track = css`
  box-sizing: border-box;
  border: none;
  height: ${secondFillthicker}px;
  background: ${trackC};
  border-radius: 8px;
`;

const trackFill = css`
  ${track};
  height: ${firstFillthicker}px;
  background-color: transparent;
  background-image: linear-gradient(${filllC}, ${filllC}),
    linear-gradient(${trackC}, ${trackC});
  background-size: var(--sx) ${firstFillthicker}px,
    calc(100% - var(--sx)) ${secondFillthicker}px;
  background-position: left center, right center;
  background-repeat: no-repeat;
`;
const inputCSS = css`
  width: 100%;
  --min: 0;
  --max: 100;
  --val: 50;
`;
const fill = css`
  height: ${trackH};
  background: ${filllC};
  border-radius: 4px;
`;

const thumb = css`
  margin-top: calc(0.5 * (${trackH} - ${thumbD}));
  cursor: pointer;
  margin-left: -15px;
  background-size: cover;
  background-color: transparent; // should add this for firefox
  background-image: url(${image});
  border: none;
  width: ${thumbD};
  height: ${thumbD};
`;

const StyledSlider = styled.input`
  &,
  &::-webkit-slider-thumb {
    -webkit-appearance: none;
  }

  &:focus {
    outline: none;
  }

  --range: calc(var(--max) - var(--min));
  --ratio: calc((var(--val) - var(--min)) / var(--range));
  --sx: calc(0.5 * ${thumbD} + var(--ratio) * (100% - ${thumbD}));

  margin: 0;
  padding: 0;
  height: ${thumbD};
  background: transparent;
  font: 1em/1 arial, sans-serif;

  &::-webkit-slider-runnable-track {
    ${trackFill};
  }

  &::-moz-range-track {
    ${track};
  }

  &::-ms-track {
    ${track};
  }

  &::-moz-range-progress {
    ${fill};
  }

  &::-ms-fill-lower {
    ${fill};
  }

  &::-webkit-slider-thumb {
    ${thumb};
  }

  &::-moz-range-thumb {
    ${thumb};
  }

  &::-ms-thumb {
    margin-top: 0;
    ${thumb};
  }

  &::-ms-tooltip {
    display: none;
  }

  &::-moz-focus-outer {
    border: 0;
  }
`;

export default (props) => <StyledSlider type="range" {...props} />;

Make sure to pass in props the onInput function and style like so

function Exam(){
  const [value, setValue] = React.useState(50);
   return (
    <App onInput={e => setValue(e.target.value)}
      style={{width: "80%","--min": 0,"--max": 100, "--val": value}} // pass the value from the props
     />
   )
}

Here is a live example

Edit Slider (styled-components) (forked)

Related