0
votes

My example usages:

Using mouse click and drag you can draw on canvas.

Using mouse wheel you can zoom-in and zoom-out.

Code: http://jsfiddle.net/cDXj3/

My question: It's possible to make that before re-drawing with function drawImage it make's smooth transition between re-drawings. If it's possible how ?

Similar code: http://jsfiddle.net/59Bhb/3/

If I use mouse zoom in drawImage function on lines 237-238:

contextBg.beginPath();
contextBg.drawImage(canvasPaint, paskutinisX, paskutinisY, imageWidthZoomed, imageHeightZoomed);

There's two canvas one with original size and hidden. Another with scaled size. After drawing on first the second get's all info to re-draw scaled drawing. After making scale change's the number of pixel grows and vice versa.

2

2 Answers

2
votes

One way is to listen for mousewheel events and scale the image...

  • based on how many times the wheel has been moved

  • and based on which direction the wheel was moved

Scale the image in small increment so that the scaling will appear smooth.

Example code and a Demo: http://jsfiddle.net/m1erickson/aCt64/

// all browsers listen for mousewheel except FF

canvas.addEventListener('mousewheel',handleMouseScrollDirection, false);
canvas.addEventListener('DOMMouseScroll',handleMouseScrollDirection,false);

// listen for mousewheel events
// rescale based on the number of times the mousewheel event has been triggered

function handleMouseScrollDirection(e){

    var direction;
    if(e.wheelDelta){
        direction=(e.wheelDelta>0)?1:-1;
    }else{
        // FF does not have e.wheelDelta (it has e.detail instead)
        // FF e.detail is negative when scrolling up
        direction=(e.detail>0)?-1:1; 
    }

    // scale the image by 10% each time the mousewheel event is triggered
    scale+=direction/10;
    draw();
}

Full example code:

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>
<script>
$(function(){

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;

    var scale=1.00;
    var iw,ih;
    var img=new Image();
    img.onload=start;
    img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house100x100.png";
    function start(){

        iw=img.width;
        ih=img.height;

        // all browsers listen for mousewheel except FF
        canvas.addEventListener('mousewheel',handleMouseScrollDirection, false);
        canvas.addEventListener('DOMMouseScroll',handleMouseScrollDirection,false);
        //
        function handleMouseScrollDirection(e){
            var direction;
            if(e.wheelDelta){
                direction=(e.wheelDelta>0)?1:-1;
            }else{
                // FF does not have e.wheelDelta (it has e.detail instead)
                // FF e.detail is negative when scrolling up
                direction=(e.detail>0)?-1:1; 
            }
            console.log(direction);
            scale+=direction/10;
            draw();
        }

        draw();
    }

    function draw(){
        ctx.clearRect(0,0,canvas.width,canvas.height);
        ctx.drawImage(img,
            0,0,iw,ih,
            (cw-iw*scale)/2,(ch-ih*scale)/2,iw*scale,ih*scale
        );
    }

}); // end $(function(){});
</script>
</head>
<body>
    <h4>Use mousewheel to scale the image smoothly.</h4>
    <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
1
votes

First thing is that to get rid of a problem, you should not hesitate to build intermediate layer to ease your work.
Here something like a 'Camera' Class could help you.

Other thing, your code to handle the scroll event is too complex. I know i repeat myself here, but i'd rather have this 19 lines long code :

var zoomSteps = [0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0];
var zoomIndex = zoomSteps.indexOf(1);

function doScroll(e) {
    e = window.event || e;
    var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
    // increase zoom index by delta
    var newZoomIndex = zoomIndex + delta;
    // return if out of bounds
    if (newZoomIndex < 0 || newZoomIndex >= zoomSteps.length) return;
    // update previous scale
    previousScale = scale;
    // we have a new valid zoomIndex
    zoomIndex = newZoomIndex;
    // check we did not reach a boundary
    zoomIsMin = (zoomIndex == 0);
    zoomIsMax = (zoomIndex == zoomSteps.length - 1);
    // compute new scale / image size
    scale = zoomSteps[zoomIndex];
    imageWidthZoomed = imageWidth * scale;
    imageHeightZoomed = imageHeight * scale;

rather than this code doing just the same thing in 140 uncommented lines, which is just 7 times bigger (or 12 times if we remove the comments from my code) :

                function doScroll(e) {
                e = window.event || e;
                var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
                if (delta === 1) {
                    if (zoom < 5) {
                        zoom++;
                        zoomIsMax = false;
                    } else {
                        zoomIsMax = true;
                    }
                } else if (delta === -1) {
                    if (zoom > -5) {
                        zoom--;
                        zoomIsMin = false;
                    } else {
                        zoomIsMin = true;
                    }
                }

                if (zoom === 1) {

                    if (delta === 1) {
                        previousScale = 1;
                    } else {
                        previousScale = 1.4;
                    }

                    scale = 1.2;
                    imageWidthZoomed = imageWidth * 1.2;
                    imageHeightZoomed = imageHeight * 1.2;
                } else if (zoom === 2) {

                    if (delta === 1) {
                        previousScale = 1.2;
                    } else {
                        previousScale = 1.6;
                    }

                    scale = 1.4;
                    imageWidthZoomed = imageWidth * 1.4;
                    imageHeightZoomed = imageHeight * 1.4;
                } else if (zoom === 3) {

                    if (delta === 1) {
                        previousScale = 1.4;
                    } else {
                        previousScale = 1.8;
                    }

                    scale = 1.6;
                    imageWidthZoomed = imageWidth * 1.6;
                    imageHeightZoomed = imageHeight * 1.6;
                } else if (zoom === 4) {

                    if (delta === 1) {
                        previousScale = 1.6;
                    } else {
                        previousScale = 2;
                    }

                    scale = 1.8;
                    imageWidthZoomed = imageWidth * 1.8;
                    imageHeightZoomed = imageHeight * 1.8;
                } else if (zoom === 5) {

                    if (delta === 1) {
                        previousScale = 1.8;
                    } else {
                        //out of range
                    }

                    scale = 2;
                    imageWidthZoomed = imageWidth * 2;
                    imageHeightZoomed = imageHeight * 2;
                } else if (zoom === 0) {

                    if (delta === 1) {
                        previousScale = 0.8;
                    } else {
                        previousScale = 1.2;
                    }

                    scale = 1;
                    imageWidthZoomed = imageWidth;
                    imageHeightZoomed = imageHeight;
                } else if (zoom === -1) {

                    if (delta === 1) {
                        previousScale = 0.6;
                    } else {
                        previousScale = 1;
                    }

                    scale = 0.8;
                    imageWidthZoomed = imageWidth * 0.8;
                    imageHeightZoomed = imageHeight * 0.8;
                } else if (zoom === -2) {

                    if (delta === 1) {
                        previousScale = 0.4;
                    } else {
                        previousScale = 0.8;
                    }

                    scale = 0.6;
                    imageWidthZoomed = imageWidth * 0.6;
                    imageHeightZoomed = imageHeight * 0.6;
                } else if (zoom === -3) {

                    if (delta === 1) {
                        previousScale = 0.2;
                    } else {
                        previousScale = 0.6;
                    }

                    scale = 0.4;
                    imageWidthZoomed = imageWidth * 0.4;
                    imageHeightZoomed = imageHeight * 0.4;
                } else if (zoom === -4) {

                    if (delta === 1) {
                        previousScale = 0.1;
                    } else {
                        previousScale = 0.4;
                    }

                    scale = 0.2;
                    imageWidthZoomed = imageWidth * 0.2;
                    imageHeightZoomed = imageHeight * 0.2;
                } else if (zoom === -5) {

                    if (delta === 1) {
                        //out of range
                    } else {
                        previousScale = 0.2;
                    }

                    scale = 0.1;
                    imageWidthZoomed = imageWidth * 0.1;
                    imageHeightZoomed = imageHeight * 0.1;
                }

The end of doScroll would definitively benefit from the use of an intermediate Camera Class.

Now for your transition, idea is the following : rather than draw on zoom change, you just record that an update is required, with its parameters :

currentDrawParameters = [canvasPaint, paskutinisX, paskutinisY, imageWidthZoomed, imageHeightZoomed];
lastChangeTime = Date.now();

then you have a separate timer that will update the canvas if need be, and why not with an easing function ( meaning : a function [0.0;1.0] -> [0.0;1.0] ).

var currentDrawParameters = null;
var transitionTime = 500;
var lastChangeTime = -1;
var easingFunction = function (x) {
    return Math.sqrt(x);  // try :  x  , x*x,  0.2+0.8*x, ... 
};

function drawSmoothly() {
    // return if no need to draw
    if (!currentDrawParameters) return;
    var timeElapsed = Date.now() - lastChangeTime;
    // return if transition ended
    if (timeElapsed > transitionTime) { 
        currentDrawParameters = null;
        return;
    }
    // compute time ratio = % time elapsed vs transitionTime
    var ratio = timeElapsed / transitionTime;
    // ease the ratio
    var easedRatio = easingFunction(ratio);
    contextBg.save();
    contextBg.globalAlpha = 0.1;
    contextBg.fillStyle = '#000';
    // erase previous image progressively
    contextBg.fillRect(0, 0, windowWidth, windowHeightUsed);
    // draw the image with an opacity 0.0  ===>>> 1.0
    contextBg.globalAlpha = easedRatio;
    contextBg.drawImage.apply(contextBg, currentDrawParameters);
    contextBg.restore();
}

setInterval(drawSmoothly, 50);

fiddle is here :

http://jsfiddle.net/gamealchemist/cDXj3/3/

try several easing function / time setting.