Pixi.js 创建围绕一个点旋转的可拖动和可点击手柄的最佳方法

Pixi.js best way to create a dragable and clickable handle that rotates around a point

我有8个手柄图形,分别代表5种不同的状态(关闭、流量1、流量2、流量3、流量4)。手柄图形 6、7 和 8 也代表流速 1、2 和 3。图像描绘了一个围绕中心点旋转的滴定管手柄。对于每个手柄状态,我需要显示匹配的纹理。我需要用户能够拖动手柄并让它在鼠标围绕中心点移动时在不同的图形中移动。我还需要用户能够点击右侧增加流量,点击左侧减少流量。

我正在考虑使用图像中的 getBounds() 并将其用作点击框,但这似乎行不通,因为我正在删除旧纹理并根据鼠标位置添加新纹理拖动。更不用说所有图像都具有相似的尺寸。

我还想创建 16 个命中框(8 个图像各 2 个,左侧 1 个用于降低流量,右侧 1 个用于增加流量)并添加和删除命中框有纹理,但这似乎过于乏味,我认为它不适用于拖动。

如果您有任何想法,请告诉我!

谢谢

拖动旋转开关

假设您获得了一个与阀门相关的鼠标坐标,例如鼠标事件 pageXpageY 属性。

您可以创建一个函数,获取元素、数字阀门步骤和鼠标坐标,并吐出您想要的值。

function getValueSetting(x, y, valveSteps, valveElement) {
    const bounds = valveElement.getBoundingClientRect();
    const centerX = (bounds.left + bounds.right) / 2;
    const centerY = (bounds.top + bounds.bottom) / 2;
    const left = x < centerX;
    const distance = Math.hypot(x - centerX, y - centerY);
    const pos = (Math.atan2(y - centerY, x - centerX) + Math.PI) / (Math.PI * 2);
    return {
        left,
        right: !left,
        distance,
        pos: Math.round(pos * valveSteps - (valveSteps / 4)),   
    };
}
         

如果阀门位置在时钟上按 1 小时递增,则 valveSteps = 12

调用函数const valveState = getValueSetting(mouseEvent.pageX, mouseEvent.pageY, 12, valveElment);

对象 returned 将具有中心 leftright 的布尔值,并且 pos 将是从 12 点钟开始的 12 个位置之一pos = 0到11点pos === 11distance 属性 是阀门的距离。

在函数中角度位置减去(valveSteps / 4) 因为Math.atan2 return 0 在3 点钟位置。减法 (valveSteps / 4) 向后旋转 1 分之一圈以在 12 点设置 0。

例子

示例绘制了5个阀位。

将鼠标移到阀门手柄(红色)上,光标将变为指针。单击并拖动鼠标以转动阀门。一旦拖动鼠标将按住阀门,直到您松开按钮。

如果不是在手柄上方,而是在阀门附近,请向左和向右单击将显示适当的消息。

const size = 64;  // size of image
const valveSteps = 12; // total number of angle steps
const valveStep = (Math.PI * 2) / valveSteps; // angle steps in radians
const startAngle = -valveStep * 2; // visual start angle of handle
const valveStart = 1; // starting pos of valve

setTimeout(() => {
    const valves = [
       createValve(64, startAngle),
       createValve(64, startAngle + valveStep),
       createValve(64, startAngle + valveStep * 2),
       createValve(64, startAngle + valveStep * 3),
       createValve(64, startAngle + valveStep * 4),
    ];

    setValve(valves[0]);
    var dragging = false;
    var currentPos = 0;
    var level = 0;
    mouse.onupdate = () => {
        const valveSetting = getValueSetting(mouse.x, mouse.y, valveSteps, valveA);
        if (valveSetting.distance < size && valveSetting.pos - valveStart === currentPos) {
            document.body.style.cursor = "pointer";
        } else {
            document.body.style.cursor = "default";
        }

        if (mouse.button && (valveSetting.distance < size || dragging)) {
            if (valveSetting.distance < size / 2 && valveSetting.pos - valveStart === currentPos) {
                if (valveSetting.pos >= valveStart && valveSetting.pos < valveStart + valves.length) {
                    dragging = true;
                }
            }
            console.clear()
            if (dragging) {
                let pos = valveSetting.pos - valveStart;
                pos = pos < 0 ? 0 : pos > valves.length - 1 ? valves.length - 1 : pos
                setValve(valves[pos]);
                currentPos = pos;
                console.log("Valve pos: " + pos);                
            } else if (valveSetting.left) {
                level --;
                console.log("Turn down " + level);
                mouse.button = false;
            } else if (valveSetting.right) {
                level ++;
                console.log("Turn up " + level);
                mouse.button = false;
            }
        } else {
            dragging = false;
        }
    }

},0);

function setValve(image) {
    valveA.innerHTML = "";
    $$(valveA, image);  // appends image to element valveA
}
function getValueSetting(x, y, valveSteps, valveElement) {
    const bounds = valveElement.getBoundingClientRect();
    const centerX = (bounds.left + bounds.right) / 2;
    const centerY = (bounds.top + bounds.bottom) / 2;
    const left = x < centerX;
    const distance = Math.hypot(x - centerX, y - centerY);
    const pos = (Math.atan2(y - centerY, x - centerX) + Math.PI) / (Math.PI * 2);
    return {
        left,
        right: !left,
        distance,
        pos: Math.round(pos * valveSteps - (valveSteps / 4)), 
    };
}

function createValve(size, angle) {
    const canvas = $("canvas", {width: size, height: size});
    const ctx = canvas.getContext("2d");
    const r = size * 0.4;
    const c = size / 2;
    ctx.strokeStyle = "red";
    ctx.lineCap = "round";
    ctx.lineWidth = 8;
    ctx.beginPath();
    ctx.lineTo(Math.cos(angle) * r + c, Math.sin(angle) * r + c);
    ctx.lineTo(-Math.cos(angle) * r * 0.2 + c, -Math.sin(angle) * r * 0.2 + c);
    ctx.stroke();
    ctx.beginPath();
    ctx.arc(c, c, 8, 0, Math.PI * 2);
    ctx.strokeStyle = "black";
    ctx.lineWidth = 2;
    ctx.stroke();


    return canvas;
}


// Boiler plate
const $ = (tag, props = {}) => Object.assign(document.createElement(tag), props);
const $$ = (p, ...sibs) => sibs.reduce((p,sib) => (p.appendChild(sib), p), p);
const mouse  = {x : 0, y : 0, button : false}
function mouseEvents(e){
      mouse.x = e.pageX;
      mouse.y = e.pageY;
      mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
    mouse.onupdate && mouse.onupdate();
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name,mouseEvents));
.valveContainer {
    position: absolute;
    top: 30px;
    left 30px;
    border: 2px solid white;
}
<div id="valveA" class="valveContainer"></div>