RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 987993
Accepted
Дмытрык
Дмытрык
Asked:2020-06-01 14:11:16 +0000 UTC2020-06-01 14:11:16 +0000 UTC 2020-06-01 14:11:16 +0000 UTC

响应式画布并悬停在对象上

  • 772

在此处输入图像描述

有一张图片...我正在尝试悬停...逻辑是这样的:

  1. 加载图像后,使用辅助画布,我得到图像的像素数组(左侧)
  2. 在画布上移动鼠标时,我确定它在图片上方的位置
  3. 之后,根据像素数组(第1项),我判断鼠标下的像素是否透明——如果不是,画一个悬停(图片右侧)

它似乎工作......,代码如下。

但是,当试图使画布适应屏幕大小(以及相应的图像)时,问题就出现了......我的悬停实现停止工作......变量scale_X和负责比例scale_Y,在代码中标度定义下方被注释掉。

谁能帮助解决问题?

const WIDTH = 800;
const HEIGHT = 374;
let scale_X = 1; // масштаб
let scale_Y = 1; // масштаб
let ctxStyles;
let hover;
const canvas = document.getElementById('canvas');
//const help = document.getElementById('helper');
const help = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const helper = help.getContext('2d');

const real_width = document.documentElement.clientWidth;
const my_width = (real_width / 100) * 70;
const my_height = my_width / 2.14;
//scale_X = my_width / WIDTH; 
 //scale_Y = my_height / HEIGHT;
canvas.width = my_width;
canvas.height = my_height;
help.width = my_width;
help.height = my_height;

ctxStyles = canvas.getBoundingClientRect();
canvas.addEventListener('mousemove', handlerMousemove);

const img = new Image();
//img.src = 'https://isstatic.askoverflow.dev/R5QJI.gif';
img.crossOrigin = "anonymous";
img.src = 'https://i.ibb.co/pfnXvqk/barraks-4-2.gif';
img.onload = function() {
  img.coords = {
    x: 40,
    y: 30
  };
  getPixArr(img);
  drawImgOnCanvas(img);
};

function drawImgOnCanvas(img) {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  const arr = [
    img,
    0,
    0,
    img.width / 2,
    img.height,
    img.coords.x * scale_X,
    img.coords.y * scale_Y,
    (img.width / 2) * scale_X,
    img.height * scale_Y
  ];
  ctx.drawImage(...arr);
  // отрисовывает 2-ю часть картинки, на которой нарисован контур
  if (hover) {
    arr[1] = img.width / 2;
    arr[3] = img.width;
    arr[5] = img.coords.x * scale_X - 1;
    arr[7] = img.width * scale_X;
    ctx.drawImage(...arr);
  }
}

function handlerMousemove(event) {
  const mouseX = event.clientX - ctxStyles.left;
  const mouseY = event.clientY - ctxStyles.top;
  hover = false;
  if (check_Mouse_On_Img(mouseX, mouseY, img)) {
    const notTransparent = check_not_transparent_pixel(mouseX, mouseY, img);
    if (notTransparent) {
      hover = true;
      drawImgOnCanvas(img);
    } else {
      drawImgOnCanvas(img);
    }
  }
}

function getPixArr(img) {
  const ctx = helper;
  const imgWidth = img.width;
  const imgHeight = img.height;
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.drawImage(
    img,
    0,
    0,
    imgWidth / 2,
    imgHeight,
    0,
    0,
    (imgWidth / 2) * scale_X,
    imgHeight * scale_Y
  );
  const pixArr = ctx.getImageData(
    0,
    0,
    (imgWidth / 2) * scale_X,
    imgHeight * scale_Y
  );
  img.pixArr = pixArr;
}

function check_Mouse_On_Img(mouseX, mouseY, img) {
  // const ctx = helper
  const topX = img.coords.x * scale_X;
  const topY = img.coords.y * scale_Y;
  const leftX = topX;
  const leftY = topY + img.pixArr.height;
  const rigthX = topX + img.pixArr.width;
  const rigthY = topY;
  const bottomX = rigthX;
  const bottomY = leftY;
  ctx.beginPath();
  // ctx.strokeStyle = 'red';
  ctx.strokeStyle = "transparent";
  ctx.moveTo(leftX, leftY);
  ctx.lineTo(topX, topY);
  ctx.lineTo(rigthX, rigthY);
  ctx.lineTo(bottomX, bottomY);
  ctx.lineTo(leftX, leftY);
  ctx.stroke();
  ctx.closePath();
  return ctx.isPointInPath(mouseX, mouseY);
}

//определяет прозрачный ли пиксель
function check_not_transparent_pixel(clientX, clientY, img) {
  const imgX = img.coords.x;
  const imgY = img.coords.y;
  // const imgX = img.coords.x * scale_X;
  // const imgY = img.coords.y * scale_Y;
  const mouseX = clientX - imgX; //т.к. массив пикселей построен из начальных координат 0:0, делаю сдвиг
  const mouseY = clientY - imgY;
  const pixArr = img.pixArr;
  const index = Math.floor(get_Pix_Index(mouseX, mouseY, pixArr));
  let alpha = pixArr.data[index + 3]; //прозрачность
  if (index > pixArr.data.length) {
    return false;
  }
  if (alpha > 0) {
    return true;
  }
  return false;

  //определяет индекс пикселя в массиве пикселей
  function get_Pix_Index(mouseX, mouseY, pixArr) {
    let pixel;
    if (mouseX == 0 && mouseY > 0) {
      pixel = (pixArr.width * scale_X) * mouseY + 1;
    } else if (mouseY == 0 && mouseX > 0) {
      pixel = mouseX;
    } else {
      pixel = mouseY * (pixArr.width* scale_X) + mouseX - 1;
    }
    let index = pixel * 4;
    return index;
  }
}

// просто для тестов
canvas.addEventListener('click', event => {
  const mouseX = event.clientX - ctxStyles.left;
  const mouseY = event.clientY - ctxStyles.top;
  console.log(mouseX, mouseY);
  ctx.beginPath();
  ctx.arc(mouseX, mouseY, 2, 0, 2 * Math.PI);
  ctx.fill();
  ctx.closePath();
  ctx.putImageData(img.pixArr, mouseX, mouseY);
});
body{
  height: 100vh;
  width: 100vw;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
canvas{
  border: 1px solid;
}
<canvas id="canvas" ></canvas>
  <canvas id="helper" style="position: fixed; left: -300%"></canvas>

PS...还有一个问题:如果使用属性隐藏辅助画布,则图像的透明像素被定义为不透明,hidden或者display: none- 为什么?

javascript
  • 2 2 个回答
  • 10 Views

2 个回答

  • Voted
  1. Best Answer
    Stranger in the Q
    2020-06-01T21:30:42Z2020-06-01T21:30:42Z

    这是旧的方式,我称之为“峰值缓冲区”,本质是在一个附加的图片中,所有的对象都被绘制成独特的颜色。

    在应用程序开始时,我制作了重复的精灵,用每个精灵唯一的颜色(标识符颜色)替换其中所有不完全透明的像素,并将它们与隐藏画布上的主画布平行绘制,当移动时,鼠标,我取颜色并确定鼠标下方的内容。


    该示例适用于悬停和拖动

    在此处输入图像描述

    let imgCtx = scene.getContext("2d");
    let pickCtx = pick.getContext("2d");
    // это важно для того чтобы сглаживанием не изменялось значение идентификатора
    pickCtx.imageSmoothingEnabled = false;
    let index = {};
    let objects = Array(22).fill(0).map((e, i) => img(i));
    let bounds = scene.getBoundingClientRect();
    let hover, drag, x, y, sx, sy;
    
    scene.addEventListener("mousemove", e => {
        drag ? handleDrag(e) : handleHover(e);
        draw();
    });
    
    scene.addEventListener("mousedown", e => {
        if (hover) {
            drag = hover;
            x = drag.x - e.pageX + bounds.left;
            y = drag.y - e.pageY + bounds.top;
            objects.splice(objects.indexOf(drag), 1);
            objects.push(drag)
            draw();
        }
    });
    
    scene.addEventListener("mouseup", e => drag = false);
    
    draw();
    
    function handleDrag(e) {
        drag.x = x + e.pageX - bounds.left
        drag.y = y + e.pageY - bounds.top 
    }
    
    function handleHover(e) {
        if (hover) 
            hover.active = false;
        let id = pickCtx.getImageData(
            e.pageX - bounds.left,
            e.pageY - bounds.top,
            1, 1
        ).data;
        hover = id[3] > 200 ? index[`${id[0]}-${id[1]}-${id[2]}`] : null;
        scene.style.cursor = hover ? "pointer" : "default";
        if (hover) 
            hover.active = true;
    }
    
    function draw() {
        pickCtx.clearRect(0, 0, scene.width, scene.height);
        imgCtx.clearRect(0, 0, scene.width, scene.height);
        objects.forEach(img => {
            imgCtx.drawImage(img.img, img.x, img.y);
            pickCtx.drawImage(img.pick, img.x, img.y);
        });
        hover && imgCtx.drawImage(hover.hover, hover.x, hover.y);
    }
    
    function rnd(){
      return Math.round(Math.random()*255)
    }
    
    function img() {    
        let id;
        while (!id || index[id.join('-')])
          id = [rnd(), rnd(), rnd()]
          
        let size = 100;  
        let img = document.createElement("canvas");
        img.width = img.height = size;
        let ctx = img.getContext("2d");
        var grd = ctx.createLinearGradient(
            size*0.1, Math.random() * size,
            size*0.9, Math.random() * size
        );
        grd.addColorStop(0, `hsl(${Math.random() * 255},55%,55%)`);
        grd.addColorStop(1, `hsl(${Math.random() * 255},55%,55%)`);
        ctx.fillStyle = grd;
        ctx.translate(size / 2, size / 2);
        ctx.rotate(Math.random() * 6);
        ctx.translate(-size / 2, -size / 2);
        let s = 20 + Math.random() * 40;
        ctx.fillRect((size - s) / 2, (size - s) / 2, s, s);
        ctx.fillRect((size - s) / 4, (size - s) / 4, s, s);
        return index[id.join('-')] = {
            x: Math.random() * (scene.width - img.width),
            y: Math.random() * (scene.height - img.height),
            id: id,
            img: img,
            pick: createPickImage(img, id),
            hover: ImageSDF(img)
        };
    }
    
    // создает изображения для рисования в пикинг буфере 
    // красит все не полностью прозрачные пиксели в цвет-идентификатор
    function createPickImage(img, pickColor) {
        let pick = document.createElement("canvas");
        let w = pick.width = img.width;
        let h = pick.height = img.height;
        let ctx = pick.getContext("2d");
        ctx.drawImage(img, 0, 0);
        var img = ctx.getImageData(0, 0, w, h);
        for (var x = 0; x < w; x++) {
            for (var y = 0; y < h; y++) {
                let o = (y * w + x) * 4;
                // если прозрачность не 0 - красим пиксель в цвет-идентификатор
                if (img.data[o + 3]) {
                    img.data[o + 0] = pickColor[0];
                    img.data[o + 1] = pickColor[1];
                    img.data[o + 2] = pickColor[2];
                    // рисование полупрозрачных пикселей в пикинг буфер - источник ошибок
                    // пусть все не полностью прозрачные пиксели будут польностью непрозрачные
                    img.data[o + 3] = 255; 
                }
            }
        }
        ctx.putImageData(img, 0, 0);
        return pick;
    }
    
    // эта функция создает signed distance field до изображения на входе
    // визуально это градиент, его мы будем использовать для обводки
    function ImageSDF(image) {
        let INF = 1e20;
        let radius = 3;
        let cutoff = 0.1;
        let canvas = document.createElement('canvas');
        let width = canvas.width = image.width;
        let height = canvas.height = image.height;
        let ctx = canvas.getContext('2d');
    
        // temporary arrays for the distance transform
        let gridOuter = new Float64Array(width * height);
        let gridInner = new Float64Array(width * height);
        let f = new Float64Array(height);
        let d = new Float64Array(width);
        let z = new Float64Array(width + 1);
        let v = new Int16Array(width);
            
        ctx.clearRect(0, 0, width, height);
        ctx.drawImage(image, 0, 0, width, height);
    
        var imgData = ctx.getImageData(0, 0, width, height);
    
        for (i = 0; i < width * height; i++) {
            imgData.data[i * 4 + 0] = imgData.data[i * 4 + 3];
            imgData.data[i * 4 + 1] = imgData.data[i * 4 + 3];
            imgData.data[i * 4 + 2] = imgData.data[i * 4 + 3];
        }
        
        for (var i = 0; i < width * height; i++) {
            var a = imgData.data[i * 4 + 1] / 255; // green channe value
            gridOuter[i] = a === 1 ? 0 : a === 0 ? INF : Math.pow(Math.max(0, 0.5 - a), 2);
            gridInner[i] = a === 1 ? INF : a === 0 ? 0 : Math.pow(Math.max(0, a - 0.5), 2);
        }
    
        edt(gridOuter, width, height, f, d, v, z);
        edt(gridInner, width, height, f, d, v, z);
    
        for (i = 0; i < width * height; i++) {
            var dd = gridOuter[i] - gridInner[i];
            let v = Math.round(255 - 255 * (dd / radius + cutoff));
            v = Math.max(0, Math.min(255, v));
            imgData.data[i * 4 + 0] = v>10 && v<245 ? 255 :0;
            imgData.data[i * 4 + 3] = v>10 && v<245 ? 255 :0;
        }
        ctx.putImageData(imgData, 0, 0);
        return canvas;
    }
    
    // 2D Euclidean distance transform by Felzenszwalb & Huttenlocher https://cs.brown.edu/~pff/dt/
    function edt(data, width, height, f, d, v, z) {
        for (var x = 0; x < width; x++) {
            for (var y = 0; y < height; y++) 
                f[y] = data[y * width + x];
            edt1d(f, d, v, z, height);
            for (y = 0; y < height; y++) 
                data[y * width + x] = d[y];
        }
        for (y = 0; y < height; y++) {
            for (x = 0; x < width; x++) 
                f[x] = data[y * width + x];
            edt1d(f, d, v, z, width);
            for (x = 0; x < width; x++) 
                data[y * width + x] = Math.sqrt(d[x]);
        }
    }
    
    // 1D squared distance transform
    function edt1d(f, d, v, z, n) {
        let INF = 1e20;
        v[0] = 0;
        z[0] = -INF;
        z[1] = +INF;
    
        for (var q = 1, k = 0; q < n; q++) {
            var s = ((f[q] + q * q) - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]);
            while (s <= z[k]) {
                k--;
                s = ((f[q] + q * q) - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]);
            }
            k++;
            v[k] = q;
            z[k] = s;
            z[k + 1] = +INF;
        }
    
        for (q = 0, k = 0; q < n; q++) {
            while (z[k + 1] < q) k++;
            d[q] = (q - v[k]) * (q - v[k]) + f[v[k]];
        }
    }
    <canvas id="scene" width="635" height="175" style="border: 1px solid"></canvas>
    <canvas id="pick"  width="635" height="175" style="display:none"></canvas>

    PS:使用有符号距离场在精灵的不透明区域添加程序描边,连线到纹理中

    UPD:通过使用 3 个颜色通道进行拾取,大大降低了出错的可能性并扩大了可能的对象数量

    UPD2:为清晰起见,将元素的形状复杂化

    相关答案:https ://ru.stackoverflow.com/a/962780/188366

    • 7
  2. Дмытрык
    2020-06-01T17:43:02Z2020-06-01T17:43:02Z

    解决了问题...正如我在评论中建议的那样,问题在于确定像素索引。但是,事实证明,需要舍入的不是收到的索引,而是鼠标坐标

    const WIDTH = 800;
    const HEIGHT = 374;
    let scale_X = 1;
    let scale_Y = 1;
    let ctxStyles;
    let hover;
    const canvas = document.getElementById('canvas');
    // const help = document.getElementById('helper');
    const help = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    const helper = help.getContext('2d');
    
    const real_width = document.documentElement.clientWidth;
    const my_width = (real_width / 100) * 70;
    const my_height = my_width / 2.14;
    scale_X = my_width / WIDTH;
    scale_Y = my_height / HEIGHT;
    canvas.width = my_width;
    canvas.height = my_height;
    help.width = my_width;
    help.height = my_height;
    ctxStyles = canvas.getBoundingClientRect();
    canvas.addEventListener('mousemove', handlerMousemove);
    
    const img = new Image();
    img.crossOrigin = "anonymous";
    img.src = 'https://i.ibb.co/pfnXvqk/barraks-4-2.gif';
    img.onload = function() {
    
      img.coords = {
    x: 40,
    y: 30
      };
      getPixArr(img);
      drawImgOnCanvas(img);
    };
    
    function drawImgOnCanvas(img) {
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
      const arr = [
    img,
    0,
    0,
    img.width / 2,
    img.height,
    img.coords.x * scale_X,
    img.coords.y * scale_Y,
    (img.width / 2) * scale_X,
    img.height * scale_Y
      ];
      ctx.drawImage(...arr);
      // отрисовывает 2-ю часть картинки, на которой нарисован контур
      if (hover) {
    arr[1] = img.width / 2;
    arr[3] = img.width;
    arr[5] = img.coords.x * scale_X - 1;
    arr[7] = img.width * scale_X;
    ctx.drawImage(...arr);
      }
    }
    
    function handlerMousemove(event) {
      const mouseX = event.clientX - ctxStyles.left;
      const mouseY = event.clientY - ctxStyles.top;
      hover = false;
      if (check_Mouse_On_Img(mouseX, mouseY, img)) {
    const notTransparent = check_not_transparent_pixel(mouseX, mouseY, img);
    if (notTransparent) {
      hover = true;
      drawImgOnCanvas(img);
    } else {
      drawImgOnCanvas(img);
    }
      }
    }
    
    function getPixArr(img) {
      const ctx = helper;
      const imgWidth = img.width;
      const imgHeight = img.height;
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
      ctx.drawImage(
    img,
    0,
    0,
    imgWidth / 2,
    imgHeight,
    0,
    0,
    (imgWidth / 2) * scale_X,
    imgHeight * scale_Y
      );
      const pixArr = ctx.getImageData(
    0,
    0,
    (imgWidth / 2) * scale_X,
    imgHeight * scale_Y
      );
      img.pixArr = pixArr;
    }
    
    function check_Mouse_On_Img(mouseX, mouseY, img) {
      // const ctx = helper
      const topX = img.coords.x * scale_X;
      const topY = img.coords.y * scale_Y;
      const leftX = topX;
      const leftY = topY + (img.pixArr.height);
      const rigthX = topX + (img.pixArr.width);
      const rigthY = topY;
      const bottomX = rigthX;
      const bottomY = leftY;
      ctx.beginPath();
      ctx.strokeStyle = 'red';
      // ctx.strokeStyle = "transparent";
      ctx.moveTo(leftX, leftY);
      ctx.lineTo(topX, topY);
      ctx.lineTo(rigthX, rigthY);
      ctx.lineTo(bottomX, bottomY);
      ctx.lineTo(leftX, leftY);
      ctx.stroke();
      ctx.closePath();
      return ctx.isPointInPath(mouseX, mouseY);
    }
    
    //определяет прозрачный ли пиксель
    function check_not_transparent_pixel(clientX, clientY, img) {
      const imgX = img.coords.x * scale_X;
      const imgY = img.coords.y * scale_Y;
      const mouseX = Math.floor(clientX - imgX); //т.к. массив пикселей построен из начальных координат 0:0, делаю сдвиг
      const mouseY = Math.floor(clientY - imgY);
      const pixArr = img.pixArr;
      const index = get_Pix_Index(mouseX, mouseY, pixArr);
      let alpha = pixArr.data[index + 3]; //прозрачность
      if (index > pixArr.data.length) {
    return false;
      }
      if (alpha > 0) {
    return true;
      }
      return false;
    
      //определяет индекс пикселя в массиве пикселей
      function get_Pix_Index(mouseX, mouseY, pixArr) {
    let pixel;
    if (mouseX == 0 && mouseY > 0) {
      pixel = pixArr.width * mouseY + 1;
    } else if (mouseY == 0 && mouseX > 0) {
      pixel = mouseX;
    } else {
      pixel = mouseY * pixArr.width + mouseX - 1;
    }
    let index =pixel * 4;
    return index;
      }
    }
    // просто для тестов
    canvas.addEventListener('click', event => {
      const mouseX = event.clientX - ctxStyles.left;
      const mouseY = event.clientY - ctxStyles.top;
      console.log(mouseX, mouseY);
      ctx.beginPath();
      ctx.arc(mouseX, mouseY, 2, 0, 2 * Math.PI);
      ctx.fill();
      ctx.closePath();
      ctx.putImageData(img.pixArr, mouseX, mouseY);
    });
    body{
      height: 100vh;
      width: 100vw;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    canvas{
      border: 1px solid;
    }
    <canvas id="canvas" ></canvas>
      

    • 1

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    根据浏览器窗口的大小调整背景图案的大小

    • 2 个回答
  • Marko Smith

    理解for循环的执行逻辑

    • 1 个回答
  • Marko Smith

    复制动态数组时出错(C++)

    • 1 个回答
  • Marko Smith

    Or and If,elif,else 构造[重复]

    • 1 个回答
  • Marko Smith

    如何构建支持 x64 的 APK

    • 1 个回答
  • Marko Smith

    如何使按钮的输入宽度?

    • 2 个回答
  • Marko Smith

    如何显示对象变量的名称?

    • 3 个回答
  • Marko Smith

    如何循环一个函数?

    • 1 个回答
  • Marko Smith

    LOWORD 宏有什么作用?

    • 2 个回答
  • Marko Smith

    从字符串的开头删除直到并包括一个字符

    • 2 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5