Skip to content

Instantly share code, notes, and snippets.

@jtebert
Created August 1, 2018 02:39
Show Gist options
  • Save jtebert/d79a854b4451622fa72fe188249a4eda to your computer and use it in GitHub Desktop.
Save jtebert/d79a854b4451622fa72fe188249a4eda to your computer and use it in GitHub Desktop.
Scalable HSV color slider with D3 and standard HTML range input
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
margin: 0;
text-align: center;
}
#channels {
display: inline-block;
margin: 64px 32px;
padding: 24px 48px;
background-color: #ffffff;
}
.channel {
height: 16px;
margin: 16px 0;
width: 300px;
position: relative;
}
.channel canvas {
display: block;
width: 100%;
height: 100%;
position: absolute;
z-index: 1;
border-radius: 8px;
}
input[type=range] {
display: block;
margin: 0;
width: 100%;
z-index: 500;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
margin: 0;
}
input[type=range]:focus {
outline: none;
outline-width: 0;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 0px;
cursor: pointer;
background: rgba(255, 255, 255, 0);
border: 0px solid rgba(1, 1, 1, 0);
}
input[type=range]::-webkit-slider-thumb {
border: 1px solid #dddddd;
height: 14px;
width: 14px;
border-radius: 14px;
background: #ffffff;
cursor: pointer;
-webkit-appearance: none;
margin-top: 0;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: rgba(255, 255, 255, 0);
}
input[type=range]::-moz-focus-outer {
border: 0;
}
input[type=range]::-moz-range-track {
width: 100%;
height: 0px;
cursor: pointer;
background: rgba(255, 255, 255, 0);
border-radius: 0px;
border: 0px solid rgba(1, 1, 1, 0);
}
input[type=range]::-moz-range-thumb {
border: 1px solid #dddddd;
height: 14px;
width: 14px;
border-radius: 14px;
background: #ffffff;
cursor: pointer;
}
input[type=range]::-ms-track {
width: 100%;
height: 0px;
cursor: pointer;
background: transparent;
border-color: transparent;
color: transparent;
}
input[type=range]::-ms-fill-lower {
background: rgba(235, 235, 235, 0);
border: 0px solid rgba(1, 1, 1, 0);
border-radius: 0px;
}
input[type=range]::-ms-fill-upper {
background: rgba(255, 255, 255, 0);
border: 0px solid rgba(1, 1, 1, 0);
border-radius: 0px;
}
input[type=range]::-ms-thumb {
border: 1px solid #dddddd;
height: 14px;
width: 14px;
border-radius: 14px;
background: #ffffff;
cursor: pointer;
height: 0px;
}
input[type=range]:focus::-ms-fill-lower {
background: rgba(255, 255, 255, 0);
}
input[type=range]:focus::-ms-fill-upper {
background: rgba(255, 255, 255, 0);
}
</style>
<div id="channels">
<div class="channel" id="h">
<canvas width="1200" height="1"></canvas>
<input type="range" min="0" max="360" value="180" class="slider" id="h-slider">
</div>
<div class="channel" id="s">
<canvas width="1200" height="1"></canvas>
<input type="range" min="0.0" max="1.0" value="0.5" step="any" class="slider" id="s-slider">
</div>
<div class="channel" id="v">
<canvas width="1200" height="1"></canvas>
<input type="range" min="0.0" max="1.0" value="0.5" step="any" class="slider" id="v-slider">
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
<script src="https://d3js.org/d3-hsv.v0.1.min.js"></script>
<script>
var h_slider = document.getElementById("h-slider");
var s_slider = document.getElementById("s-slider");
var v_slider = document.getElementById("v-slider");
var white = d3.rgb("white"),
black = d3.rgb("black"),
width = d3.select("canvas").property("width");
var channels = {
h: { scale: d3.scale.linear().domain([0, 360]).range([0, width]), x: width / 2 },
s: { scale: d3.scale.linear().domain([0, 1]).range([0, width]), x: width / 2 },
v: { scale: d3.scale.linear().domain([0, 1]).range([0, width]), x: width / 2 }
};
channels.h.x = width * h_slider.value / 360.;
channels.s.x = width * s_slider.value / 1.;
channels.v.x = width * v_slider.value / 1.;
var out_color = d3.hsv(
channels.h.scale.invert(channels.h.x),
channels.s.scale.invert(channels.s.x),
channels.v.scale.invert(channels.v.x),
);
document.body.style.backgroundColor = out_color;
h_slider.oninput = function () {
channels.h.x = width * this.value / 360.;
channel.select("canvas").each(render);
out_color.h = this.value;
document.body.style.backgroundColor = out_color;
}
s_slider.oninput = function () {
channels.s.x = width * this.value / 1.;
channel.select("canvas").each(render);
out_color.s = this.value;
document.body.style.backgroundColor = out_color;
}
v_slider.oninput = function () {
channels.v.x = width * this.value / 1.;
channel.select("canvas").each(render);
out_color.v = this.value;
document.body.style.backgroundColor = out_color;
}
var channel = d3.selectAll(".channel")
.data(d3.entries(channels));
var canvas = channel.select("canvas")
.call(d3.behavior.drag().on("drag", dragged))
.each(render);
function dragged(chan) {
// When a channel bar is dragged, get the x position of the cursor
// And render all three channels accordingly
chan.value.x = Math.max(0, Math.min(this.width - 1, d3.mouse(this)[0]));
console.log(chan.value.x)
canvas.each(render);
}
function render(chan) {
// d: key : h | s | l
// value : scale : scale()
// x : pixel position
var width = this.width,
context = this.getContext("2d"),
image = context.createImageData(width, 1),
i = -1;
// Current color in HSL, from each channel's x-value
var current = d3.hsv(
channels.h.scale.invert(channels.h.x),
channels.s.scale.invert(channels.s.x),
channels.v.scale.invert(channels.v.x)
);
for (var x = 0, v, color; x < width; ++x) {
current[chan.key] = chan.value.scale.invert(x);
color = d3.rgb(current);
image.data[++i] = color.r;
image.data[++i] = color.g;
image.data[++i] = color.b;
image.data[++i] = 255;
}
context.putImageData(image, 0, 0);
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment