Search code examples
javascriptjqueryhtmlcanvastooltip

html5 canvas tooltip javascript flickering


I've got a problem with canvas tooltip on html5

Everything seems to work smooth, but when i calculate the canvas tooltip left/up nearby the canvas right corner(to show the tooltip not outside the canvas), then he starts with flickering.

Sometimes he does not even show the tooltip, while the x and y coordinate are the same as last time.

html aspx page code:

<canvas id="CanvasToolTip"  width="275" height="75" style="z-index: 3"></canvas>

Javascript:

var canvasToolTip = canvasLayer = document.getElementById("CanvasToolTip"); //Get the canvasToolTip object
var canvasToolTipContent = canvasToolTip.getContext("2d");



var handleMouseOverForToolTip = function (e) {
    e.preventDefault();
    e.stopPropagation();

    if (listPoints.length > 0) {
        var canvasOffset = $("#CanvasPlan").offset();
        var offsetX = canvasOffset.left;
        var offsetY = canvasOffset.top;

        var tooltipXCorrection = 10;

        var tooltipHeight = 75;
        var tooltipWidth = 275;

        var canvasPlanHeight = canvasPlan.getHeight();
        var canvasPlanWidth = canvasPlan.getWidth();

        var mouseX = parseInt(e.pageX - offsetX);
        var mouseY = parseInt(e.pageY - offsetY);

        var enableToolTip = false;
        for (var i = 0; i < listPoints.length; i++) {
            var point = listPoints[i];
            var dx = mouseX - point.xZ;
            var dy = mouseY - point.yZ;

            if (dx * dx + dy * dy < toolTipRadius && point.draw === true) {
                var diffX = canvasPlanWidth - (point.xZ + tooltipWidth);
                var diffY = canvasPlanHeight - (point.yZ + tooltipHeight);

                //checks if toolbar difference is more then -120, then change the position more to left
                if (diffX < -120) {
                    canvasToolTip.style.left = (point.xZ + (diffX / 2 + diffX / 3)) + "px";
                } else if (diffX <= -90) {
                    canvasToolTip.style.left = (point.xZ + (diffX / 2 + diffX / 4)) + "px";
                } else {
                    canvasToolTip.style.left = (point.xZ + tooltipXCorrection) + "px";
                }

                if (diffY < -5) {
                    canvasToolTip.style.top = (point.yZ - tooltipHeight) + "px";
                } else {
                    canvasToolTip.style.top = (point.yZ) + "px";
                }

                canvasToolTipContent.clearRect(0, 0, canvasToolTip.width, canvasToolTip.height);
                canvasToolTipContent.fillStyle = "white";
                canvasToolTipContent.font = "13px Verdana";
                canvasToolTipContent.fillText(pointName + ": " + point.name, 10, 20);
                enableToolTip = true;
            }
        }

        if (!enableToolTip) {
            canvasToolTip.style.left = "-500px";
        }
    }
};

Maybe the handleMouseOverForToolTip function is doing to many calculations?


Solution

  • Sync to display refresh to stop flicker and shearing.

    The most common cause of flickering and shearing for animated and often presented graphics is being out of sync with the display refresh.

    To display an image the display hardware scans through display RAM presenting one pixel at a time to the display. This process takes time and if you present for display something new it will not be displayed until the next time those pixels are scanned. If in the interim something else is drawn over that location, eg the background, then the next scan may show the background. Depending on the timing you will get the background some frames and not others. The result is flickering.

    If the graphics you are displaying gets presented while the display is scanning the same area you may only see half the rendering. This is called shearing (in animation shearing will cut a moving object on a line across the screen at some random (or regular) point).

    Sync using requestAnimationFrame

    To fix the Flicker and Shearing you need to sync to the display and ensure that everything being rendered is presented to the display RAM at the start of the next frame, not half way through or randomly such as for input events.

    To sync to the display use requestAnimationFrame. It ensures that what you render to display elements (such as a canvas) will only be presented to the display at the start of the next display refresh.

    Mouse events

    For your problem you should use the mouse event to just record the relevant mouse details and use a animation loop to display the tooltip in sync with the refresh.

    const mouse = {
        x : 0,
        y : 0,
        update : true,  // to prevent unneeded updates of the display
    }
    function mouseEvent(event){
        if(event.pageX !== mouse.x || event.pageY !== mouse.y){
             mouse.update = true;
             mouse.x = event.pageX;
             mouse.y = event.pageY;
         }
     }
    

    Display

    The display loop is called via requestAnimationFrame. This on most systems is at 60fps but can be higher in some rare cases. But the rate is irrelevant for this problem. All that matters is that the browser knows what is rendered in the function needs to be deferred until display refresh.

    function displayLoop(time){ // time is hi res time passed by the browser
        if(mouse.update){  // has the position of the mouse changed
            mouse.update = false; // clear the update semaphore
            createToolTip(); // render the tool tip 
        }
        requestAnimationFrame(displayLoop); // request next animation frame
        // the function exits now. Normally at this point the changed elements
        // are presented to the display RAM but as this is a special function
        // call the changed DOM elements are held in the offscreen buffer 
        // until the next display refresh
    }
    requestAnimationFrame(displayLoop); // start displaying
    

    Depending on what else is being rendered, you may have something else over writing the tooltip you may have to update the tooltip every frame rather than just when the mouse has moved. Just modify the above to ignore the mouse update semaphore.