Overlapping range inputs. On click change input with closest value - javascript

I have two overlapping range inputs, this creates a multi range input effect.
I want it so that whenever a click is made on either of these, the input with the closest value to the newly clicked value, is changed. Not entirely sure how to go about this.
How could I do this?
(function() {
"use strict";
var supportsMultiple = self.HTMLInputElement && "valueLow" in HTMLInputElement.prototype;
var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
self.multirange = function(input) {
if (supportsMultiple || input.classList.contains("multirange")) {
return;
}
var values = input.getAttribute("value").split(",");
var max = +input.max || 100;
var ghost = input.cloneNode();
input.classList.add("multirange", "original");
ghost.classList.add("multirange", "ghost");
input.value = values[0] || max / 2;
ghost.value = values[1] || max / 2;
input.parentNode.insertBefore(ghost, input.nextSibling);
Object.defineProperty(input, "originalValue", descriptor.get ? descriptor : {
// Dang you Safari >:(
get: function() {
return this.value;
},
set: function(v) {
this.value = v;
}
});
Object.defineProperties(input, {
valueLow: {
get: function() {
return Math.min(this.originalValue, ghost.value);
},
set: function(v) {
this.originalValue = v;
},
enumerable: true
},
valueHigh: {
get: function() {
return Math.max(this.originalValue, ghost.value);
},
set: function(v) {
ghost.value = v;
},
enumerable: true
}
});
if (descriptor.get) {
// Again, fuck you Safari
Object.defineProperty(input, "value", {
get: function() {
return this.valueLow + "," + this.valueHigh;
},
set: function(v) {
var values = v.split(",");
this.valueLow = values[0];
this.valueHigh = values[1];
},
enumerable: true
});
}
function update() {
ghost.style.setProperty("--low", input.valueLow * 100 / max + 1 + "%");
ghost.style.setProperty("--high", input.valueHigh * 100 / max - 1 + "%");
}
input.addEventListener("input", update);
ghost.addEventListener("input", update);
update();
}
multirange.init = function() {
Array.from(document.querySelectorAll("input[type=range][multiple]:not(.multirange)")).forEach(multirange);
}
if (document.readyState == "loading") {
document.addEventListener("DOMContentLoaded", multirange.init);
} else {
multirange.init();
}
})();
#supports (--css: variables) {
input[type="range"].multirange {
-webkit-appearance: none;
padding: 0;
margin: 0;
display: inline-block;
vertical-align: top;
width: 250px;
margin-top: 50px;
margin-left: 50px;
background: lightblue;
}
input[type="range"].multirange.original {
position: absolute;
}
input[type="range"].multirange.original::-webkit-slider-thumb {
position: relative;
z-index: 2;
}
input[type="range"].multirange.original::-moz-range-thumb {
transform: scale(1);
/* FF doesn't apply position it seems */
G z-index: 1;
}
input[type="range"].multirange::-moz-range-track {
border-color: transparent;
/* needed to switch FF to "styleable" control */
}
input[type="range"].multirange.ghost {
position: relative;
background: var(--track-background);
--track-background: linear-gradient(to right, transparent var(--low), var(--range-color) 0, var(--range-color) var(--high), transparent 0) no-repeat 0 45% / 100% 40%;
--range-color: hsl(190, 80%, 40%);
}
input[type="range"].multirange.ghost::-webkit-slider-runnable-track {
background: var(--track-background);
}
input[type="range"].multirange.ghost::-moz-range-track {
background: var(--track-background);
}
}
<input type="range" multiple value="10,80" />

You'll have to capture a mouse event on the element and calculate how close it is to the high marker vs. the low marker and decide which one to update based on that. Also, because these are two stacked input elements, you'll probably have to pass the event to the low range input manually.
Here's my go at creating such a function:
function passClick(evt) {
// Are the ghost and input elements inverted? (ghost is lower range)
var isInverted = input.valueLow == ghost.value;
// Find the horizontal position that was clicked (as a percentage of the element's width)
var clickPoint = evt.offsetX / this.offsetWidth;
// Map the percentage to a value in the range (note, assumes a min value of 0)
var clickValue = max * clickPoint;
// Get the distance to both high and low values in the range
var highDiff = Math.abs(input.valueHigh - clickValue);
var lowDiff = Math.abs(input.valueLow - clickValue);
if (lowDiff < highDiff && !isInverted || (isInverted && lowDiff > highDiff)) {
// The low value is closer to the click point than the high value
// We should update the low value input
var passEvent = new MouseEvent("mousedown", {screenX: evt.screenX, clientX: evt.clientX});
// Pass a new event to the low "input" element (which is obscured by the
// higher "ghost" element, and doesn't get mouse events outside the drag handle
input.dispatchEvent(passEvent);
// The higher "ghost" element should not respond to this event
evt.preventDefault();
return false;
}
else {
console.log("move ghost");
// The high value is closer to the click point than the low value
// The default behavior is appropriate, so do nuthin
}
}
ghost.addEventListener("mousedown", passClick);
I put this code immediately above the input.addEventListener("input", update); line in your sample, and it seems to work. See my fiddle.
Some provisos though:
I only tested in Chrome. IE might have some trouble based on how I replicated the event. It may use a mechanism other than dispatchEvent... like fireEvent or something.
Initially I coded it assuming that the "ghost" element always kept track of the high range. I've since updated things to invert the event dispatching when the ghost element has the lower value--but I sped through it.

Here's something simple you could use. Although you might want to customize the style.
I am altering the z-index of the slider element based upon its proximity to the cursor.
JSFiddle
HTML
<input id='a' type='range' />
<input id='b' type='range' />
<label role='info'></label>
JS
var a = document.getElementById('a');
var b = document.getElementById('b');
a.onmousemove = function(e) {
MouseMove.call(a, e);
};
b.onmousemove = function(e) {
MouseMove.call(b, e);
};
var MouseMove = function(eventArg)
{
var max = parseInt(a.max),
min = parseInt(a.min),
diff = max - min,
clickPoint = eventArg.offsetX / a.offsetWidth,
clickPointVal = parseInt(diff * clickPoint) + min;
/* absolute distance from respective slider values */
var da = Math.abs(a.value - clickPointVal),
db = Math.abs(b.value - clickPointVal);
// Making the two sliders appear above one another only when no mouse button is pressed, this condition may be removed at will
if (!eventArg.buttons)
{
if (da < db)
{
a.style.zIndex = 2;
b.style.zIndex = 1;
}
else if (db < da)
{
b.style.zIndex = 2;
a.style.zIndex = 1;
}
}
document.querySelector('label').innerHTML = 'Red: ' + a.value + ', Green: ' + b.value + ', X: ' + eventArg.clientX;
}
CSS
input {
margin: 0px;
position: absolute;
left: 0px;
}
label {
display: inline-block;
margin-top: 100px;
}
#a {
z-index: 2;
}
#b {
z-index: 1;
}

Related

How do I see if one element is touching another in JavaScript without clicking anything

OK so I've tried one thing from a different question and it worked, but not the way I wanted it to. it didn't work the way I wanted it to! You literally had to click when two objects were touching so it would alert you, if somebody can figure out a way to detect if two elements are touching without having to click that would be a life saver! So I hope you people who read this request please respond if you know how. this is the code below. so one object is moving and i want it to make it stop when the object hits the player (i am making a game) the movement is by px.... i want it to keep testing if one object hits the player, and if it does i want it to stop everything.
var boxes = document.querySelectorAll('.box');
boxes.forEach(function (el) {
if (el.addEventListener) {
el.addEventListener('click', clickHandler);
} else {
el.attachEvent('onclick', clickHandler);
}
})
var detectOverlap = (function () {
function getPositions(elem) {
var pos = elem.getBoundingClientRect();
return [[pos.left, pos.right], [pos.top, pos.bottom]];
}
function comparePositions(p1, p2) {
var r1, r2;
if (p1[0] < p2[0]) {
r1 = p1;
r2 = p2;
} else {
r1 = p2;
r2 = p1;
}
return r1[1] > r2[0] || r1[0] === r2[0];
}
return function (a, b) {
var pos1 = getPositions(a),
pos2 = getPositions(b);
return comparePositions(pos1[0], pos2[0]) && comparePositions(pos1[1], pos2[1]);
};
})();
function clickHandler(e) {
var elem = e.target,
elems = document.querySelectorAll('.box'),
elemList = Array.prototype.slice.call(elems),
within = elemList.indexOf(elem),
touching = [];
if (within !== -1) {
elemList.splice(within, 1);
}
for (var i = 0; i < elemList.length; i++) {
if (detectOverlap(elem, elemList[i])) {
touching.push(elemList[i].id);
}
}
if (touching.length) {
console.log(elem.id + ' touches ' + touching.join(' and ') + '.');
alert(elem.id + ' touches ' + touching.join(' and ') + '.');
} else {
console.log(elem.id + ' touches nothing.');
alert(elem.id + ' touches nothing.');
}
}
this is my video game right now (please do not copy)
<!DOCTYPE html>
/
<html>
<form id="player" class="box">
</form>
<button type="button" class="up" onclick="moveup()">^</button>
<button type="button" class="down" onclick="movedown()">v
</button>
<style src="style.css">
#player {
width: 300px;
height: 100px;
background-color: blue;
display: inline-block;
position: relative;
bottom: -250px;
left: 200px;
}
.up {
position: relative;
bottom: -400px;
}
.down {
position: relative;
bottom: -420px;
}
body {
background-color: black;
}
#car {
width: 300px;
height: 100px;
background-color: red;
display: inline-block;
position: relative;
bottom: -250px;
left: 600px;
}
</style>
<form id="car" class="box"></form>
<script>
imgObj = document.getElementById('player');
imgObj.style.position= 'relative';
imgObj.style.bottom = '-250px';
function moveup() {
imgObj.style.position= 'relative';
imgObj.style.bottom = '-250px';
imgObj.style.bottom = parseInt(imgObj.style.bottom) + 70 + 'px';
}
function movedown() {
imgObj.style.position= 'relative';
imgObj.style.bottom = '-250px';
imgObj.style.bottom = parseInt(imgObj.style.bottom) + -120 + 'px';
}
myMove();
function myMove() {
var elem = document.getElementById("car");
var pos = 0;
var id = setInterval(frame, 5);
function frame() {
if (pos == 1000) {
clearInterval(id);
myMove();
} else {
pos++;
elem.style.left = pos + "px";
elem.style.left = pos + "px";
}
}
}
/* please do not copy; this is it so far i want the red box when it hits the player(blue box) to stop everything that is happening */
/* made by Jsscripter; car game */
</script>
</html>
Intersection observer. API was largely developed because of news feeds and infinite scrolling. Goal was to solve when something comes into view, load content. Also is a great fit for a game.
The Intersection Observer API lets code register a callback function
that is executed whenever an element they wish to monitor enters or
exits another element (or the viewport), or when the amount by which
the two intersect changes by a requested amount. This way, sites no
longer need to do anything on the main thread to watch for this kind
of element intersection, and the browser is free to optimize the
management of intersections as it sees fit.
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
All major browsers except safari support the API. For backwards compatibility and Safari support can use the polyfill from W3C found here. Check out this example from MDN:
var callback = function(entries, observer) {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
};
var options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
var observer = new IntersectionObserver(callback, options);
var target = document.querySelector('#listItem');
observer.observe(target);
See this in action here: https://codepen.io/anon/pen/OqpeMV

Consistent scroll behavior across browsers

I made this pen to simulate the issue.
function createBox() {
var box = document.createElement("div");
box.className = "box";
box.style.backgroundColor = "#"+((1<<24)*Math.random()|0).toString(16);
return box;
}
function prependInnerHTML() {
console.log('prependInnerHTML');
var element = document.getElementById('scroll');
var box = createBox();
element.innerHTML = box.outerHTML + element.innerHTML;
element.prepend(box);
}
function prependPrepend() {
console.log('prependPrepend');
var element = document.getElementById('scroll');
var box = createBox();
element.prepend(box);
}
function prependPrepend() {
console.log('prependPrepend');
var element = document.getElementById('scroll');
var box = createBox();
element.prepend(box);
}
function prependInsertBefore() {
console.log('prependInsertBefore');
var element = document.getElementById('scroll');
var box = createBox();
element.insertBefore(box, element.firstChild);
}
function scroll() {
console.log('scroll');
var detailElement = document.getElementById('details');
var scrollElement = document.getElementById('scroll');
detailElement.innerHTML = "ScrollTop: " + scrollElement.scrollTop;
}
function clearScrollElement() {
console.log('clear');
var element = document.getElementById('scroll');
while(element.firstChild){
element.removeChild(element.firstChild);
}
}
html, body {
margin: 0;
padding: 0;
height: 100%;
width: 100%
}
#details {
position: fixed;
top: 10px;
left: 20px;
background-color: white;
}
#options {
position: fixed;
top: 10px;
right: 20px;
background-color: white;
}
#scroll {
overflow-x: hidden;
overflow-y: scroll;
height: 100%;
width: 100%;
}
.box {
height: 100vh;
width: 100%
}
<div id="options">
<span onclick="prependInnerHTML()">InnerHTML</span>
<span onclick="prependInsertBefore()">InsertBefore</span>
<span onclick="prependPrepend()">Prepend</span>
<span onclick="clearScrollElement()">Clear</span>
</div>
<div id="details">
ScrollTop: 0
</div>
<div id="scroll" onscroll="scroll()"></div>
The issue is that when a element gets prepended to a scrollable element, it will have different behaviors across different browsers.
innerHTML:
The first prepend method is changing the innerHTML of the scrollable element. This seems to be consistent across multiple browsers.
The problem is that frameworks like Vue don't internally use this method, it probably uses one of the other methods.
InsertBefore with Chrome:
If the scrollTop is 0 and we prepend the scrollTop stays zero. If the scrollTop is higher than zero it will adjust the scrollTop to include the height of the prepended element.
InsertBefore with IE/Edge/Firefox:
Same behavior as the innerHTML one.
Prepend:
Prepend seems te be unsupported on IE/Edge so i'll skip that one.
Question:
How do I make the InsertBefore behave the same across all the browsers, without introducing all kinds of browser checks?
Try doing this
/**
* Adds a new element to the top of the container, If after is a valid element then the new element is added after that element
* #param {HTMLElement} newItem - newItem to be added
* #param {HTMLElement} container - container
* #param {HTMLElement} [after=] - optional parameter, states that the new item will be added after this element
* #returns {HTMLElement}
*/
function addElementToTop(newItem, container, after) {
if (after instanceof HTMLElement === false || after.parentElement !== container) {
var first = container.firstElementChild;
} else {
first = after.nextElementSibling;
}
if (first !== null) {
container.insertBefore(newItem, first);
} else {
container.appendChild(newItem);
}
return newItem;
}
Here is the simple version
function addElementToTop(newItem, container) {
var first = container.firstElementChild;
if (first) container.insertBefore(newItem, first);
else container.appendChild(newItem);
return newItem;
}
The below version maintains the same view-port but the scroll changes
function addElementToTopSteadyScroll(newItem, container) {
var bot = container.scrollHeight - container.scrollTop;
var first = container.firstElementChild;
if (first) container.insertBefore(newItem, first);
else container.appendChild(newItem);
container.scrollTop = container.scrollHeight - bot;
return newItem;
}
Whereas the below version maintains the same scroll but the view-port changes
function addElementToTopFlowScroll(newItem, container) {
var top = container.scrollTop;
var first = container.firstElementChild;
if (first) container.insertBefore(newItem, first);
else container.appendChild(newItem);
container.scrollTop = top;
return newItem;
}

Jump/scroll to #id within a scroll element [duplicate]

I have a div that has overflow: scroll and I have some elements inside the DIV that are hidden. On click of a button on the page, I want to make the DIV scroll to a particular element inside the DIV.
How do I achieve this?
You need to read the offsetTop property of the div you need to scroll to and then set that offset to the scrollTop property of the container div. Bind this function the event you want to :
function scrollToElementD(){
var topPos = document.getElementById('inner-element').offsetTop;
document.getElementById('container').scrollTop = topPos-10;
}
div {
height: 200px;
width: 100px;
border: 1px solid black;
overflow: auto;
}
p {
height: 80px;
background: blue;
}
#inner-element {
background: red;
}
<div id="container"><p>A</p><p>B</p><p>C</p><p id="inner-element">D</p><p>E</p><p>F</p></div>
<button onclick="scrollToElementD()">SCROLL TO D</button>
function scrollToElementD(){
var topPos = document.getElementById('inner-element').offsetTop;
document.getElementById('container').scrollTop = topPos-10;
}
Fiddle : http://jsfiddle.net/p3kar5bb/322/ (courtesy #rofrischmann)
Just improved it by setting a smooth auto scrolling inside a list contained in a div
https://codepen.io/rebosante/pen/eENYBv
var topPos = elem.offsetTop
document.getElementById('mybutton').onclick = function () {
console.log('click')
scrollTo(document.getElementById('container'), topPos-10, 600);
}
function scrollTo(element, to, duration) {
var start = element.scrollTop,
change = to - start,
currentTime = 0,
increment = 20;
var animateScroll = function(){
currentTime += increment;
var val = Math.easeInOutQuad(currentTime, start, change, duration);
element.scrollTop = val;
if(currentTime < duration) {
setTimeout(animateScroll, increment);
}
};
animateScroll();
}
//t = current time
//b = start value
//c = change in value
//d = duration
Math.easeInOutQuad = function (t, b, c, d) {
t /= d/2;
if (t < 1) return c/2*t*t + b;
t--;
return -c/2 * (t*(t-2) - 1) + b;
};
I guess it may help someone :)
Here's a simple pure JavaScript solution that works for a target Number (value for scrollTop), target DOM element, or some special String cases:
/**
* target - target to scroll to (DOM element, scrollTop Number, 'top', or 'bottom'
* containerEl - DOM element for the container with scrollbars
*/
var scrollToTarget = function(target, containerEl) {
// Moved up here for readability:
var isElement = target && target.nodeType === 1,
isNumber = Object.prototype.toString.call(target) === '[object Number]';
if (isElement) {
containerEl.scrollTop = target.offsetTop;
} else if (isNumber) {
containerEl.scrollTop = target;
} else if (target === 'bottom') {
containerEl.scrollTop = containerEl.scrollHeight - containerEl.offsetHeight;
} else if (target === 'top') {
containerEl.scrollTop = 0;
}
};
And here are some examples of usage:
// Scroll to the top
var scrollableDiv = document.getElementById('scrollable_div');
scrollToTarget('top', scrollableDiv);
or
// Scroll to 200px from the top
var scrollableDiv = document.getElementById('scrollable_div');
scrollToTarget(200, scrollableDiv);
or
// Scroll to targetElement
var scrollableDiv = document.getElementById('scrollable_div');
var targetElement= document.getElementById('target_element');
scrollToTarget(targetElement, scrollableDiv);
You need a ref to the div you wish to scroll to inner-div and a ref to the scrollable div scrollable-div:
const scrollToDiv = () => {
const innerDivPos = document.getElementById('inner-div').offsetTop
document
.getElementById('scrollable-div')
.scrollTo({ top: innerDivPos, behavior: 'smooth' })
}
<div id="scrollable-div" style="height:100px; overflow-y:auto;">
<button type="button" style="margin-bottom:500px" onclick="scrollToDiv()">Scroll To Div</button>
<div id="inner-div">Inner Div</div>
</div>

Make cursor stay in boundary

I have a color-wheel picker (which I took a good chunk of code from this library). I'm trying to get the color-wheels cursor to not go out of boundaries. I don't want it going pass the gray border.
I can do the obvious and make the parent div: overflow:hidden, but that would just hide the cursor, it wouldn't make it not go pass the boundaries.
I think the relevant variable to edit, is one of the following (in the hsvMove function, starting on line 39):
var r = currentTargetHeight / 2,
x = e.pageX - startPoint.left - r,
y = e.pageY - startPoint.top - r
How can I get the cursor to not go past the boundaries?
JSFiddle
(function(window) {
"use strict"
// Some common use variables
var myColor = new Colors(),
startPoint,
currentTarget,
currentTargetHeight = 0,
PI = Math.PI,
PI2 = PI * 2;
/* ---------------------------------- */
/* ---- HSV-circle color picker ----- */
/* ---------------------------------- */
var colorDiskWrapper = document.getElementById('colorDiskWrapper'),
colorDiskCursor = document.getElementById('colorDiskCursor'),
colorDisk = document.getElementById('colorDisk');
var colorDiscRadius = colorDisk.offsetHeight / 2;
// Create Event Functions
var hsvDown = function(e) { // mouseDown callback
var target = e.target || e.srcElement;
if (e.preventDefault) e.preventDefault();
if (target === colorDiskCursor || target === colorDisk) {
currentTarget = target.parentNode;
} else
return;
startPoint = getOrigin(currentTarget);
currentTargetHeight = currentTarget.offsetHeight;
addEvent(window, 'mousemove', hsvMove);
hsvMove(e);
startRender();
},
hsvMove = function(e) { // mouseMove callback
var r = currentTargetHeight / 2,
x = e.pageX - startPoint.left - r,
y = e.pageY - startPoint.top - r,
h = 360 - ((Math.atan2(y, x) * 180 / PI) + (y < 0 ? 360 : 0)),
s = (Math.sqrt((x * x) + (y * y)) / r) * 100;
myColor.setColor({
h: h,
s: s
}, 'hsv');
},
renderHSVPicker = function(color) { // used in renderCallback of 'new ColorPicker'
var x = Math.cos(PI2 - color.hsv.h * PI2),
y = Math.sin(PI2 - color.hsv.h * PI2),
r = color.hsv.s * colorDiscRadius;
// Position the Cursor
colorDiskCursor.style.left = (x * r + colorDiscRadius) + 'px';
colorDiskCursor.style.top = (y * r + colorDiscRadius) + 'px';
};
addEvent(colorDiskWrapper, 'mousedown', hsvDown); // event delegation
addEvent(window, 'mouseup', function() {
removeEvent(window, 'mousemove', hsvMove);
stopRender();
});
var doRender = function(color) {
renderHSVPicker(color);
},
renderTimer,
startRender = function(oneTime) {
if (oneTime) { // only Colors is instanciated
doRender(myColor.colors);
} else {
renderTimer = window.setInterval(
function() {
doRender(myColor.colors);
// http://stackoverflow.com/questions/2940054/
}, 13); // 1000 / 60); // ~16.666 -> 60Hz or 60fps
}
},
stopRender = function() {
window.clearInterval(renderTimer);
};
/*
Function Helpers
*/
function getOrigin(elm) {
var box = (elm.getBoundingClientRect) ? elm.getBoundingClientRect() : {
top: 0,
left: 0
},
doc = elm && elm.ownerDocument,
body = doc.body,
win = doc.defaultView || doc.parentWindow || window,
docElem = doc.documentElement || body.parentNode,
clientTop = docElem.clientTop || body.clientTop || 0, // border on html or body or both
clientLeft = docElem.clientLeft || body.clientLeft || 0;
return {
left: box.left + (win.pageXOffset || docElem.scrollLeft) - clientLeft,
top: box.top + (win.pageYOffset || docElem.scrollTop) - clientTop
};
}
function addEvent(obj, type, func) {
addEvent.cache = addEvent.cache || {
_get: function(obj, type, func, checkOnly) {
var cache = addEvent.cache[type] || [];
for (var n = cache.length; n--;) {
if (obj === cache[n].obj && '' + func === '' + cache[n].func) {
func = cache[n].func;
if (!checkOnly) {
cache[n] = cache[n].obj = cache[n].func = null;
cache.splice(n, 1);
}
return func;
}
}
},
_set: function(obj, type, func) {
var cache = addEvent.cache[type] = addEvent.cache[type] || [];
if (addEvent.cache._get(obj, type, func, true)) {
return true;
} else {
cache.push({
func: func,
obj: obj
});
}
}
};
if (!func.name && addEvent.cache._set(obj, type, func) || typeof func !== 'function') {
return;
}
if (obj.addEventListener) obj.addEventListener(type, func, false);
else obj.attachEvent('on' + type, func);
}
function removeEvent(obj, type, func) {
if (typeof func !== 'function') return;
if (!func.name) {
func = addEvent.cache._get(obj, type, func) || func;
}
if (obj.removeEventListener) obj.removeEventListener(type, func, false);
else obj.detachEvent('on' + type, func);
}
})(window);
#colorDisk {
background-image: url("http://i.imgur.com/tX5NbWs.png");
width: 350px;
height: 350px;
background-repeat: no-repeat;
cursor: pointer;
}
#colorDiskCursor {
position: absolute;
border: 2px solid black;
border-radius: 50%;
width: 9px;
height: 9px;
cursor: pointer;
}
<div id="colorDiskWrapper">
<div id="colorDisk"></div>
<div id="colorDiskCursor"></div>
</div>
<script src="https://rawgit.com/PitPik/colorPicker/master/colors.js"></script>
Problem:
Let's start by outlining the algorithm, so we are all clear about what we are trying to do: with each mouse move/click, calculate the H and S values represented by the mouse position relative to the color disk (of a HSV color system). Periodically, render the disk cursor exactly on the position corresponding to the H and S values.
There are a few things we need to take note of:
The actual radius that we should use to calculate the color values (S in particular) is the radius of the color disk minus the radius of the cursor, because we want to prevent the cursor leaving the boundary of the color disk. In this case, we have 175px - 6.5px, which is 168.5px.
When rendering the cursor, we are setting its top-left position. We need to offset the position by its radius so that our "finger pointer" appears nicely in the middle of the cursor.
Solution:
With the above understanding, the solution is straightforward.
There are problems with your code, since you are using the entire radius of the color disk (175px), without accounting for the radius of the disk cursor (6.5px).
A few things you should also fix / consider in your code:
Your currentTargetHeight is the height of the wrapper (350px), which is then halved to derive the r. This looks wrong to me. You should not be concerned with the wrapper's dimension at all. Remove this variable from the code. The values we need to be concerned with should be colorDiskRadius and colorDiskCursorRadius.
Your colorDiscCursor is set to position: absolute, but its offset parent is not the wrapper, since wrapper is not a positioned element. Hence, the top-left position that we set for colorDiscCursor may be totally unpredictable, depending where its actual parent is on an actual page. To solve this, set wrapper to position: relative.
I notice that you are not setting box-sizing (defaults to content-box), which is why your cursor is actually 13px wide despite having width: 9px; likewise for height. I personally like to use box-sizing: border-box so that when I have to do pixel-accurate calculations, I just need to look at the actual width and height properties in CSS, without having to also refer to padding and border.
Minor issue: You sometimes use disc, and sometimes disk in your code. Try to standardise this for sanity's sake.
TL;DR
Here's the fiddle implementing 1, 2 and 4: https://jsfiddle.net/hrnn9w9k/4/
I didn't include 3 as it might not be your preference, but I strongly recommend it.

Draggable Columns With Pure JavaScript

I'm trying to build a draggable column based layout in JavaScript and having a bit of hard time with it.
The layout comprises of 3 columns (divs), with two dragable divs splitting each. The idea is that they are positioned absolutely and as you drag the draggers, the columns' respective widths, and left values are updated.
The three columns should always span the full width of the browser (the right most column is 100% width), but the other two should remain static by default when the browser is resized (which is why i'm using px, not %).
My code isn't working as of yet, I'm relatively new to JavaScript (which is why I don't want to use jQuery).
Having said that, there must be a more efficient (and cleaner) way of achieving this with less code that works (without reaching for the $ key).
If anyone with some awesome JS skills can help me out on this I'd be super-appreciative.
Here's the fiddle I'm working on http://jsfiddle.net/ZFwz5/3/
And here's the code:
HTML
<!-- colums -->
<div class="col colA"></div>
<div class="col colB"></div>
<div class="col colC"></div>
<!-- draggers -->
<div class="drag dragA" style="position: absolute; width: 0px; height: 100%; cursor: col-resize; left:100px;"><div></div></div>
<div class="drag dragB" style="position: absolute; width: 0px; height: 100%; cursor: col-resize; left: 300px;"><div></div></div>
CSS:
body {
overflow:hidden;
}
.col {
position: absolute;
height:100%;
left: 0;
top: 0;
overflow: hidden;
}
.colA {background:red;width:100px;}
.colB {background:green; width:200px; left:100px;}
.colC {background:blue; width:100%; left:300px;}
.drag > div {
background: 0 0;
position: absolute;
width: 10px;
height: 100%;
cursor: col-resize;
left: -5px;
}
and my terrible JavaScript:
//variabe columns
var colA = document.querySelector('.colA');
var colB = document.querySelector('.colB');
var colC = document.querySelector('.colC');
//variable draggers
var draggers = document.querySelectorAll('.drag');
var dragA = document.querySelector(".dragA");
var dragB = document.querySelector(".dragB");
var dragging = false;
function drag() {
var dragLoop;
var t = this;
var max;
var min;
if (dragging = true) {
if (this == dragA) {
min = 0;
max = dragB.style.left;
} else {
min = dragA.style.left;
max = window.innerWidth;
}
dragLoop = setInterval(function () {
var mouseX = event.clientX;
var mouseY = event.clientY;
if (mouseX >= max) {
mouseX = max;
}
if (mouseY <= min) {
mouseY = min;
}
t.style.left = mouseX;
updateLayout();
}, 200);
}
}
function updateLayout() {
var posA = dragA.style.left;
var posB = dragB.style.left;
colB.style.paddingRight = 0;
colA.style.width = posA;
colB.style.left = posA;
colB.style.width = posB - posA;
colC.style.left = posB;
colC.style.width = window.innerWidth - posB;
}
for (var i = 0; i < draggers.length; i++) {
draggers[i].addEventListener('mousedown', function () {
dragging = true;
});
draggers[i].addEventListener('mouseup', function () {
clearInterval(dragLoop);
dragging = false;
});
draggers[i].addEventListener('mouseMove', function () {
updateLayout();
drag();
});
}
I see a couple of things wrong here. First of all, the mousemove event only fires on an element when the mouse is over that element. You might have better luck registering a mousemove listener on the parent of your div.drag elements, then calculating the mouse's position inside that parent whenever a mouse event happens, then using that position to resize your columns and your draggers.
Second, I'm not quite sure what you're trying to do by registering a function with setInterval. You're doing pretty well with registering event listeners; why not continue to use them to change the state of your DOM? Why switch to a polling-based mechanism? (and the function you pass to setInterval won't work anyway - it refers to a variable named event, which in that context is undefined.)
This is just a little example... I hope it can help you :)
window.onload = function() {
var myDiv = document.getElementById('myDiv');
function show_coords(){
var monitor = document.getElementById('monitor');
var x = event.clientX - myDiv.clientWidth / 2;
var y = event.clientY - myDiv.clientWidth / 2;
monitor.innerText = "X: " + x + "\n" + "Y: " + y;
myDiv.style.left = x + "px";
myDiv.style.top = y + "px";
}
document.onmousemove = function(){
if(myDiv.innerText == "YES"){show_coords();}
}
myDiv.onmousedown = function(){
myDiv.innerText = "YES";
}
myDiv.onmouseup = function(){
myDiv.innerText = "NO";
}
}

Categories