Search code examples
javascriptjqueryhtmlhtml5-canvasz-index

Assign higher z-index to mask images


We are Fetching mask image from JSON & displaying inside Container

enter image description here

Onclick Mask image, we are allowing to upload another image on mask as below, also user can drag the uploaded image as in Codepen1, everything was fine...

enter image description here

Now Client want to display mask image above Box image, so I displayed mask image on top of box image as below :

Issue :

enter image description here

when I click on mask image [left side], higher z-index value assign to box image, so box image will override mask image. because mask image & box image are overlapped with each other.

when I click on mask image [right side], then there is no problem...

Also, I can able to drag the uploaded image successfully from the right side of mask image but not from the left side.

Z-index code :

 container.enable = function() {
        draggable = true;
        $(canvas).attr("active", "true");
        div.css({
            "z-index": 2
        });
    }

    container.disable = function() {
        draggable = false;
        $(canvas).attr("active", "false");
        div.css({
            "z-index": 1
        });
    }

container.selected = function(ev) {
            var pos = container.mousePosition(ev);
            var item = $(".masked-img canvas").filter(function() {
                var offset = $(this).offset()
                var x = pos.x - offset.left;
                var y = pos.y - offset.top;
                var d = this.getContext('2d').getImageData(x, y, 1, 1).data;
                return d[0] > 0
            });

            JQmasks.forEach(function(el) {
                var id = item.length > 0 ? $(item).attr("id") : "";
                if (el.id == id)
                    el.item.enable();
                else el.item.disable();
            });
        };

Snippet :

var target;
const imageUrl = "";

let jsonData = {
    "layers": [{
        "name": "design3",
        "x": 0,
        "y": 0,
        "width": 300,
        "height": 300,
        "layers":[  
            {  
               "type":"image",
               "src":"14IVyVb.png",
               "name":"bg_img",
               "x":0,
               "y":0,
               "width":200,
               "height":200
            },
            {  
               "type":"image",
               "src":"l8vA9bB.png",
               "name":"mask_image",
               "x":100,
               "y":15,
               "width":180,
               "height":166            
        }]
    }]
};

const containerElement = $('#container');
const fileUp = $('#fileup');
let mask;

$(function() {

    // Upload image onclick mask image 

    containerElement.click(function(e) {
        // filtering out non-canvas clicks
        if (e.target.tagName !== 'CANVAS') return;

        // getting absolute points relative to container
        const absX = e.offsetX + e.target.parentNode.offsetLeft + e.currentTarget.offsetLeft;
        const absY = e.offsetY + e.target.parentNode.offsetTop + e.currentTarget.offsetTop;

        const $canvasList = $(this).find('canvas');
        // moving all canvas parents on the same z-index
        $canvasList.parent().css({
           zIndex: 0
        });

        $canvasList.filter(function() { // filtering only applicable canvases
            const bbox = this.getBoundingClientRect();
            const canvasTop = bbox.top + window.scrollY;
            const canvasLeft = bbox.left + window.scrollX;
            return (
                absX >= canvasLeft && absX <= canvasLeft + bbox.width &&
                absY >= canvasTop && absY <= canvasTop + bbox.height)
        }).each(function() { // checking white in a click position
            const x = absX - this.parentNode.offsetLeft - e.currentTarget.offsetLeft;
            const y = absY - this.parentNode.offsetTop - e.currentTarget.offsetTop;
            const pixel = this.getContext('2d').getImageData(x, y, 1, 1).data;
            if (pixel[3] === 255)
            {
                if (!$(e.currentTarget).data('image_set' + target))
                    $(this).parent().css({
                        zIndex: 2
                    })
                target = this.id;
                console.log(target);
                if (!$(e.currentTarget).data('image_set' + target))
                    setTimeout(fileUp.click.bind(fileUp), 20);
            }
        })
    });

    // Fetch mask images from json file - IGNORE this code 

    function getAllSrc(layers) {
        let arr = [];
        layers.forEach(layer => {
            if (layer.src) {
                arr.push({
                    src: layer.src,
                    x: layer.x,
                    y: layer.y,
                    height: layer.height,
                    width: layer.width,
                    name: layer.name
                });
            } else if (layer.layers) {
                let newArr = getAllSrc(layer.layers);
                if (newArr.length > 0) {
                    newArr.forEach(({
                        src,
                        x,
                        y,
                        height,
                        width,
                        name
                    }) => {
                        arr.push({
                            src,
                            x: (layer.x + x),
                            y: (layer.y + y),
                            height,
                            width,
                            name: (name)
                        });
                    });
                }
            }
        });
        return arr;
    }

    function json(data) {
        var width = 0;
        var height = 0;

        let arr = getAllSrc(data.layers);
        let layer1 = data.layers;
        width = layer1[0].width;
        height = layer1[0].height;
        let counter = 0;
        let table = [];

        // container dimensions 
        containerElement.css('width', width + "px").css('height', height + "px").addClass('temp');
        //end 

        for (let {
                src,
                x,
                y,
                name
            } of arr) {

            //Get Height and width of mask image [ edit button ] 
            var ImagePosition = arr;
            //code end 

            var imageUrl1 = imageUrl;

            var mask = $(".container").mask({
                imageUrl: name.indexOf('mask_') !== -1 ? imageUrl1 : undefined,

                // Fetch Mask images 
                maskImageUrl: 'http://i.imgur.com/' + src,
                // end 

                onMaskImageCreate: function(img) {
                    // Mask image positions 
                    img.css({
                        "position": "absolute",
                        "left": x + "px",
                        "top": y + "px"
                    });
                    // end 

                },
                id: counter
            });
            // here 

            ImagePosition.map(function(cur, index) {
                var available = cur.name.includes('mask_');

                if (!available) {
                    $('.masked-img' + index).css('pointer-events', 'none');
                }
            });

            table.push(mask);
            fileup.onchange = function() {

                let mask2 = table[target];
                const newImageLoadedId = mask2.loadImage(URL.createObjectURL(fileup.files[0]));
                document.getElementById('fileup').value = "";

            };
            counter++;
        }
        return mask;
    }
    mask = json(jsonData);
}); // end of function 

// Image code 

(function($) {
    window.JQmasks = [];
    $.fn.mask = function(options) {
        // This is the easiest way to have default options. 
        var settings = $.extend({
            // These are the defaults. 
            maskImageUrl: undefined,
            imageUrl: undefined,
            scale: 1,
            id: new Date().getUTCMilliseconds().toString(),
            x: 0, // image start position 
            y: 0, // image start position 
            onMaskImageCreate: function(div) {},
        }, options);

        // Create the image properties
        settings.maskImage = new Image
        settings.image = new Image

        // set the cross-origin attributes
        settings.maskImage.setAttribute('crossOrigin', 'anonymous');
        settings.image.setAttribute('crossOrigin', 'anonymous');

        settings.maskImage.onload = function() {
            // once the mask is loaded, load the image
            container.loadImage(settings.imageUrl)
            container.drawMask()
        }

        settings.image.onload = function() {
            // once the image is loaded, render to canvas

            container.drawImage()
        }

        var container = $(this);

        let prevX = 0,
            prevY = 0,
            draggable = false,
            img,
            canvas,
            context,
            image,
            timeout,
            initImage = false,
            startX = settings.x,
            startY = settings.y,
            div;

        container.mousePosition = function(event) {
            return {
                x: event.pageX || event.offsetX,
                y: event.pageY || event.offsetY
            };
        }


         container.selected = function(ev) {
            var pos = container.mousePosition(ev);
            var item = $(".masked-img canvas").filter(function() {
                var offset = $(this).offset()
                var x = pos.x - offset.left;
                var y = pos.y - offset.top;
                var d = this.getContext('2d').getImageData(x, y, 1, 1).data;
                return d[0] > 0
            });

            JQmasks.forEach(function(el) {
                var id = item.length > 0 ? $(item).attr("id") : "";
                if (el.id == id)
                    el.item.enable();
                else el.item.disable();
            });
        };

        container.enable = function() {
            draggable = true;
            $(canvas).attr("active", "true");
            div.css({
                "z-index": 2
            });
        }

        container.disable = function() {
            draggable = false;
            $(canvas).attr("active", "false");
            div.css({
                "z-index": 1
            });
        }      

        container.drawMask = function() {
            if (!settings.maskImage) return true;
            canvas.width = settings.maskImage.width;
            canvas.height = settings.maskImage.height;
            context.save();
            context.beginPath();
            context.globalCompositeOperation = "source-over";
            // draw the masked image after scaling 
            if (settings.maskImage) context.drawImage(settings.maskImage, 0, 0, settings.maskImage.width, settings.maskImage.height);
            context.restore()
        };

         container.drawImage = function() {
            const img = settings.image

            settings.x = settings.x == 0 && initImage ? (canvas.width - (img.width * settings.scale)) / 2 : settings.x;
            settings.y = settings.y == 0 && initImage ? (canvas.height - (img.height * settings.scale)) / 2 : settings.y;

            context.globalCompositeOperation = 'source-atop';
            context.save();
            context.translate(settings.x + img.width / 2, settings.y + img.height / 2);
            context.rotate(settings.rotate);
            context.scale(settings.scale, settings.scale);
            context.translate(-(settings.x + img.width / 2), -(settings.y + img.height / 2));
            let width = img.width,
                height = img.height;
            if (img)
                context.drawImage(img, settings.x, settings.y, width, height);
            context.restore();
            initImage = false;           
        }

        // change the draggable image 
		
		container.onDragStart = function(evt) {
            // console.log('Draw started');
            if (evt.target.getContext) {
                var pixel = evt.target.getContext('2d').getImageData(evt.offsetX, evt.offsetY, 1, 1).data;

                $(canvas).attr("active", "true");
                container.selected(evt);
                prevX = evt.clientX;
                prevY = evt.clientY;
                var img = new Image();
                evt.originalEvent.dataTransfer.setDragImage(img, 10, 10);
                evt.originalEvent.dataTransfer.setData('text/plain', 'anything');

            }
        };

        container.onDragOver = function(evt) {
            // console.log('Drag over');
            if (evt.target.getContext) {
                var pixel = evt.target.getContext('2d').getImageData(evt.offsetX, evt.offsetY, 1, 1).data;
                if (pixel[3] === 255) {
                    if (draggable && $(canvas).attr("active") === "true") {
                        var x = settings.x + evt.clientX - prevX;
                        var y = settings.y + evt.clientY - prevY;
                        if (x == settings.x && y == settings.y)
                            return; // position has not changed
                        settings.x += evt.clientX - prevX;
                        settings.y += evt.clientY - prevY;
                        prevX = evt.clientX;
                        prevY = evt.clientY;
                        //logTMatrix(settings);
                        clearTimeout(timeout);
                        timeout = setTimeout(function() {
                            container.drawMask();
                            container.drawImage();
                        }, 1);
                    }
                } else {
                    evt.stopPropagation();
                    return false;
                }
            }
        };


        container.loadImage = function(imageUrl) {
            if (!imageUrl) return true;
            settings.y = startY;
            settings.x = startX;
            prevX = prevY = 0;

            initImage = true;

            settings.image.src = imageUrl; // CHANGED

            // sirpepole Add this 
            return settings.id;
        };

        container.loadMaskImage = function(imageUrl, from) {

            canvas = document.createElement("canvas");
            context = canvas.getContext('2d');
            canvas.setAttribute("draggable", "true");
            canvas.setAttribute("id", settings.id);
            // settings.maskImageUrl = imageUrl;
            settings.maskImage.src = imageUrl // CHANGED

            div = $("<div/>", {
                "class": "masked-img"
            }).append(canvas);

            // div.find("canvas").on('touchstart mousedown', function(event) 
            div.find("canvas").on('dragstart', function(event) {
                if (event.handled === false) return;
                event.handled = true;
                container.onDragStart(event);
            });

            div.find("canvas").on('touchend mouseup', function(event) {
                if (event.handled === false) return;
                event.handled = true;
                container.selected(event);
            });

            div.find("canvas").bind("dragover", container.onDragOver);

            container.append(div);
            if (settings.onMaskImageCreate)
                settings.onMaskImageCreate(div);

            // container.loadImage(settings.imageUrl);
            // Moved this to the settings.maskImage.onload
        };
        if (settings.maskImageUrl) {
            container.loadMaskImage(settings.maskImageUrl);
        }
        JQmasks.push({
            item: container,
            id: settings.id
        })
        // Edit image 
        div.addClass('masked-img' + settings.id);
        div.attr('data-id', settings.id);
        // ends 
        return container;
    };
}(jQuery));
.container {
        background: silver;
        position: relative;
    }
    
    .container img {
        position: absolute;
        top: 0;
        bottom: 250px;
        left: 0;
        right: 0;
        margin: auto;
        z-index: 999;
    }
    
    .masked-img {
        overflow: hidden;
        position: relative;
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js">
</script>

<input id="fileup" name="fileup" type="file" style="display:none">

<div id="container" class="container">
</div>

Here is Video link & Code in Pastebin & Fiddle & Codepen


Solution

  • I am confused by the overall implementation and there are multiple lines terminated without semicolon. Anyway, the root cause is inside container.selected(). You don't need to implement your way to find which canvas is selected. You attach the event by using div.find("canvas").on('touchend mouseup', function(event) {...}; and the browser will trigger the event with the correct target for you. When the event is triggered, the listener will be invoked. The target is given in the event object (namely event.currentTarget). That is the spirit of event listener.

    The original container.selected() predicts which canvas is touched by using mouse pointer position. Since box image and mask image have certain area overlaped, the result may contains two canvas and z-index is applied to the wrong one.

    I modify container.selected() as follow

    container.selected = function(ev) {
        var pos = container.mousePosition(ev);
        var item = ev.currentTarget;
    
        JQmasks.forEach(function(el) {
            var id = $(item).attr("id");
            if (el.id == id)
                el.item.enable();
            else
                el.item.disable();
        });
    };