将div定位在精确光标位置

12
我正在使用这个颜色选择器,我试图将一个div作为拖动器添加到画布中,而不是嵌入其中。我通过这些答案使其工作。
问题是,拖动器与光标有点偏离。显然的解决方案是从拖动器左侧顶部位置中减去一些数值。像这样:
dragger.style.left = (currentX + radiusPlusOffset - 13) + 'px';
dragger.style.top = (currentY + radiusPlusOffset - 13) + 'px';

当我减去13时,另一个问题出现了。如果你将控制器拖到最右边或最下面,它不会完全到达那里。如果你将它拖到最左边或者最上面,它会超过画布的边界。

基本上,我想实现的是,让可拖动物在光标指针的精确位置,并且不能超过画布的边界。我该如何实现这个目标呢?

JSFiddle

var b = document.body;
var c = document.getElementsByTagName('canvas')[0];
var a = c.getContext('2d');


var wrapper = document.getElementById('wrapper');
var dragger = document.createElement('div');
dragger.id = 'dragger';
wrapper.appendChild(dragger);
wrapper.insertBefore(dragger, c);


document.body.clientWidth; // fix bug in webkit: http://qfox.nl/weblog/218

(function() {

  // Declare constants and variables to help with minification
  // Some of these are inlined (with comments to the side with the actual equation)
  var doc = document;
  doc.c = doc.createElement;
  b.a = b.appendChild;

  var width = c.width = c.height = 400,
    label = b.a(doc.c("p")),
    input = b.a(doc.c("input")),
    imageData = a.createImageData(width, width),
    pixels = imageData.data,
    oneHundred = input.value = input.max = 100,
    circleOffset = 0,
    diameter = width - circleOffset * 2,
    radius = diameter / 2,
    radiusPlusOffset = radius + circleOffset,
    radiusSquared = radius * radius,
    two55 = 255,
    currentY = oneHundred,
    currentX = -currentY,
    wheelPixel = circleOffset * 4 * width + circleOffset * 4;

  // Math helpers
  var math = Math,
    PI = math.PI,
    PI2 = PI * 2,
    sqrt = math.sqrt,
    atan2 = math.atan2;

  // Setup DOM properties
  b.style.textAlign = "center";
  label.style.font = "2em courier";
  input.type = "range";

  // Load color wheel data into memory.
  for (y = input.min = 0; y < width; y++) {
    for (x = 0; x < width; x++) {
      var rx = x - radius,
        ry = y - radius,
        d = rx * rx + ry * ry,
        rgb = hsvToRgb(
          (atan2(ry, rx) + PI) / PI2, // Hue
          sqrt(d) / radius, // Saturation
          1 // Value
        );

      // Print current color, but hide if outside the area of the circle
      pixels[wheelPixel++] = rgb[0];
      pixels[wheelPixel++] = rgb[1];
      pixels[wheelPixel++] = rgb[2];
      pixels[wheelPixel++] = d > radiusSquared ? 0 : two55;
    }
  }
  a.putImageData(imageData, 0, 0);

  // Bind Event Handlers
  input.onchange = redraw;
  dragger.onmousedown = c.onmousedown = doc.onmouseup = function(e) {
    // Unbind mousemove if this is a mouseup event, or bind mousemove if this a mousedown event
    doc.onmousemove = /p/.test(e.type) ? 0 : (redraw(e), redraw);
  }



  // Handle manual calls + mousemove event handler + input change event handler all in one place.
  function redraw(e) {
    // Only process an actual change if it is triggered by the mousemove or mousedown event.
    // Otherwise e.pageX will be undefined, which will cause the result to be NaN, so it will fallback to the current value
    currentX = e.pageX - c.offsetLeft - radiusPlusOffset || currentX;
    currentY = e.pageY - c.offsetTop - radiusPlusOffset || currentY;

    // Scope these locally so the compiler will minify the names.  Will manually remove the 'var' keyword in the minified version.
    var theta = atan2(currentY, currentX),
      d = currentX * currentX + currentY * currentY;

    // If the x/y is not in the circle, find angle between center and mouse point:
    //   Draw a line at that angle from center with the distance of radius
    //   Use that point on the circumference as the draggable location
    if (d > radiusSquared) {
      currentX = radius * math.cos(theta);
      currentY = radius * math.sin(theta);
      theta = atan2(currentY, currentX);
      d = currentX * currentX + currentY * currentY;
    }

    label.textContent = b.style.background = hsvToRgb(
      (theta + PI) / PI2, // Current hue (how many degrees along the circle)
      sqrt(d) / radius, // Current saturation (how close to the middle)
      input.value / oneHundred // Current value (input type="range" slider value)
    )[3];

    dragger.style.left = (~~currentX + radiusPlusOffset - 13) + 'px';
    dragger.style.top = (~~currentY + radiusPlusOffset - 13) + 'px';
    // Reset to color wheel and draw a spot on the current location. 


    // Draw the current spot.
    // I have tried a rectangle, circle, and heart shape.
    /*
    // Rectangle:
    a.fillStyle = '#000';
    a.fillRect(currentX+radiusPlusOffset,currentY+radiusPlusOffset, 6, 6);
    */

    // Circle:
    /*a.beginPath();  
    a.strokeStyle = 'white';
    a.arc(~~currentX+radiusPlusOffset,~~currentY+radiusPlusOffset, 4, 0, PI2);
    a.stroke();*/


    // Heart:
    //a.font = "1em arial";
    //a.fillText("♥", currentX + radiusPlusOffset - 4, currentY + radiusPlusOffset + 4);

  }

  // Created a shorter version of the HSV to RGB conversion function in TinyColor
  // https://github.com/bgrins/TinyColor/blob/master/tinycolor.js
  function hsvToRgb(h, s, v) {
    h *= 6;
    var i = ~~h,
      f = h - i,
      p = v * (1 - s),
      q = v * (1 - f * s),
      t = v * (1 - (1 - f) * s),
      mod = i % 6,
      r = [v, q, p, p, t, v][mod] * two55,
      g = [t, v, v, q, p, p][mod] * two55,
      b = [p, p, t, v, v, q][mod] * two55;

    return [r, g, b, "rgb(" + ~~r + "," + ~~g + "," + ~~b + ")"];
  }

  // Kick everything off
  redraw(0);

  /*
  // Just an idea I had to kick everything off with some changing colors…
  // Probably no way to squeeze this into 1k, but it could probably be a lot smaller than this:
  currentX = currentY = 1;
  var interval = setInterval(function() {
      currentX--;
      currentY*=1.05;
      redraw(0)
  }, 7);
    
  setTimeout(function() {
      clearInterval(interval)
  }, 700)
  */

})();
#c {
  border: 7px solid black;
  border-radius: 50%;
}
#wrapper {
  width: 400px;
  height: 400px;
  position: relative;
  cursor: pointer;
}
#wrapper:active {
  //cursor: none;

}
#dragger {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: block;
  position: absolute;
  border: 2px solid black;
}
<div id='wrapper'>
  <canvas id="c"></canvas>
</div>


顺带一提,我建议在CSS中的 #wrapper 中添加 -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; user-select: none;,这样用户不会误操作选择整个画布放入蓝色正方形。 - Andrew Bone
1个回答

5

你只是在错误的位置进行减法。
不要从元素位置中减去,直接从鼠标指针位置中减去。

这段代码实际上移动了该元素,使其相对于指针位移,并使其看起来超出了颜色选择器的边框。

dragger.style.left = (~~currentX + radiusPlusOffset - 13) + 'px';
dragger.style.top = (~~currentY + radiusPlusOffset - 13) + 'px';

...这并不是你真正想要的,你想要指针的计算数字恰好位于dragger元素的中心,因此你应该从指针位置提取,这样dragger的限制不会受到影响,并且它仍然保持在色彩选择器的边界内。

currentX = e.pageX - c.offsetLeft - radiusPlusOffset -13 || currentX;
currentY = e.pageY - c.offsetTop - radiusPlusOffset -13 || currentY;

演示


谢谢回答!如果您删除边框,然后将拖动条全部向下拖动,它将跑到画布外面:https://jsfiddle.net/ebq1usb9/ - Jessica
@Jessica - 你应该考虑更多的因素,比如任何边框的宽度、偏移量等,并创建一个圆来保持拖动器内部匹配等。类似这样的东西-> https://jsfiddle.net/adeneo/ebq1usb9/1/ - adeneo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接