Created
August 1, 2018 02:39
-
-
Save jtebert/d79a854b4451622fa72fe188249a4eda to your computer and use it in GitHub Desktop.
Scalable HSV color slider with D3 and standard HTML range input
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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