mouseMove function not mapping properly when object-fit:contain used in CSS

115 views Asked by At

I'm using code that allows a user to select a color from an image when the mouse hovers that pixel. When using object-fit:contain for the image the colors do not map correctly. The script is acting as if the image is stretched to 100% width / 100% height and not taking into account the object-fit:contain style.

    <html>
    <head>
    <style>
    *,*:after,*:before{
      -webkit-box-sizing:border-box;
      -moz-box-sizing:border-box;
      box-sizing:border-box;
    }
    
    body{
      width:100%;
      height:100%;
      position:relative;
    }
    
    .thumbnail{
      position:relative;
      display: block;
      overflow:hidden;
      height:100%;
      width:100%;
    }
    
    .thumbnail img {
      display: block;
      cursor: crosshair;
      height: 100%;
      width: 100%;
      object-fit: contain;
    }
    
    #follow {
      position: absolute;
      width: 55px;
      height: 55px;
      border-radius:80%;
      z-index: 99 !important;
      border:2px solid black;
    }
    
    #cs{ 
      display:none;
    }
    
    #color{
      display:none;
    }
    
    }
    </style>
    </head>
    <body>
    <form id=color>
    <input id="hex" type="text"  value="hex">
    </form>
    
    <div class="preview" id="follow"></div>
    <div class="thumbnail">
    
    <img src='[BASE64 IMAGE GOES HERE]'/>       
          </div>
        
          <canvas id="cs"></canvas>
        <script>
        // preview follow mouse
        $(document).mousemove(function(e) {
          $("#follow").css({
            left: e.offsetX,
            top: e.offsetY
          });
        });
        
        // vars
        var img = _('.thumbnail img'),
            canvas = _('#cs'),
            preview = _('.preview'),x = '',y = '';
        
        // click function
        img.addEventListener('click', function(e){
          // chrome
          if(e.offsetX) {
            x = e.offsetX;
            y = e.offsetY; 
          }
          // firefox
          else if(e.layerX) {
            x = e.layerX;
            y = e.layerY;
          }
          useCanvas(canvas,img,function(){
            // get image data
            var p = canvas.getContext('2d')
            .getImageData(x, y, 1, 1).data;
            // show info
         
        color.innerHTML = '<textarea id="hex" type="text"  >'+rgbToHex(p[0],p[1],p[2])+'</textarea>';
        
          });
        },false);
        
        // preview function mousemove
        img.addEventListener('mousemove', function(e){
          // chrome
          if(e.offsetX) {
            x = e.offsetX;
            y = e.offsetY; 
          }
          // firefox
          else if(e.layerX) {
            x = e.layerX;
            y = e.layerY;
          }
          
          useCanvas(canvas,img,function(){
            
            // get image data
            var p = canvas.getContext('2d')
            .getImageData(x, y, 1, 1).data;
            // show preview color
            preview.style.background = rgbToHex(p[0],p[1],p[2]);
          });
        },false);
        
        
        // canvas function
        function useCanvas(el,image,callback){
          el.width = image.width; // img width
          el.height = image.height; // img height
          // draw image in canvas tag
          el.getContext('2d')
          .drawImage(image, 0, 0, image.width, image.height);
          return callback();
        }
        // short querySelector
        function _(el){
          return document.querySelector(el);
        };
        
        // convert rgba to hex 
        // http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
        function componentToHex(c) {
          var hex = c.toString(16);
          return hex.length == 1 ? "0" + hex : hex;
        }
        function rgbToHex(r, g, b) {
          return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
        }
        
        function findPos(obj) {
            var curleft = 0, curtop = 0;
            if (obj.offsetParent) {
                do {
                    curleft += obj.offsetLeft;
                    curtop += obj.offsetTop;
                } while (obj = obj.offsetParent);
                return { x: curleft, y: curtop };
            }
            return undefined;
        }
        </script>
        
        </body>
        </html>

1

There are 1 answers

3
Alan H. On

Yes, that is because the code is using an unscaled canvas copy of the image.

The surest way to fix this is to always display the image at 100% scale instead of using fit. Allow panning around the image if needed so that the user can still click any part of it. (You may need to subtract the offset dimensions of the pan from the xy coordinates of the mouse event.)

You could also detect the resized scale and then use this to multiply the offsets when used in canvas, but this is going to lead to inaccuracies due to rounding and so on.

You may also want to avoid browsers smoothing the displayed image to avoid displaying pixels that don’t actually exist in the source image: image-rendering: pixelated https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering