Pregunta

I've two svg elements on which I've applied a mouseover/mouseout event. The goal is to increase the radius of the mask on mouseover to a size specified by a variable (maxMaskRadius) and decrease it on mouseout back to the initial state (initialMaskRadius).

I works perfectly with just one element. But when I've two elements and hover from one element to another, the animation from the previous elements aborts immediately. But I'd like to have it animate back to its initial state. With my current code that's unfortunately not possible.

Any suggestions on how to do that proper?

DEMO

CSS:

.dday.highlight .overlay {
    fill: rgba(247,99,62,0.8);
}

.dday.normal {
    width: 288px;
    height: 288px;
}

HTML:

<svg class="dday highlight normal" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-image="car.jpg">

    <image height="196" width="250" />

    <a class="overlay" xlink:href="/svg/index.html" target="_top">
        <rect x="0" y="0" width="288" height="288" style="mask: url(#mask1)" onmouseover="initAnimation(evt)" onmouseout="initAnimation(evt)" />
    </a>

    <mask id="mask1">
        <rect x="0" y="0" width="288" height="288" fill="#fff" />
        <circle cx="125" cy="125" r="25" />
    </mask>

</svg>


<svg class="dday highlight normal" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-image="nokia.jpg">

    <image height="196" width="250" />

    <a class="overlay" xlink:href="/svg/index.html" target="_top">
        <rect x="0" y="0" width="288" height="288" style="mask: url(#mask2)" onmouseover="initAnimation(evt)" onmouseout="initAnimation(evt)" />
    </a>

    <mask id="mask2">
        <rect x="0" y="0" width="288" height="288" fill="#fff" />
        <circle cx="125" cy="125" r="25" />
    </mask>

</svg>

JS:

var maxImageWidth = 250,
    maxImageHeight = 196,
    ease = 50,
    speed = 12,
    maxMaskRadius = 100,

    svg = null,
    svgWidth = null,
    svgHeight = null,
    mask = null,
    maskRadius = null,
    initialMaskRadius = null,
    imageObj = [],
    imageSrcs = [],
    imageWidth = null,
    imageHeight = null,
    mouseEvent = null;

init();

function init(el, index) {
    $('.dday').each(function(index){
        defineCurrentElement(this, index);
        positionMask();
    });
}

function defineCurrentElement(el, index) {
    // Redefine the current Element
    svg = $(el).closest('.dday'),
    svgWidth = svg.width(),
    svgHeight = svg.height(),
    mask = svg.find('circle')[0];

    // On page load there is a index provided to load the images for each element
    if(typeof index !== 'undefined'){
        loadImage(index);
    }
}

function loadImage(index) {
    // Load images and scale them to fit the predefined area
    imageSrcs[index] = svg.data('image');

    imageObj[index] = new Image(),
    imageObj[index].image = $('image')[index];

    imageObj[index].onload = function(){
        scale_width = maxImageWidth / this.width;
        scale_height = maxImageHeight / this.height;

        scale = Math.min(scale_width, scale_height);

        imageWidth = this.width * scale;
        imageHeight = this.height * scale;

        var xCoordinate = (svgWidth - imageWidth) / 2,
            yCoordinate = (svgHeight - imageHeight) / 2;

        this.image.setAttributeNS('http://www.w3.org/1999/xlink','href', imageSrcs[index]);
        this.image.setAttributeNS(null,'width', imageWidth);
        this.image.setAttributeNS(null,'height', imageHeight);
        this.image.setAttributeNS(null,'x', xCoordinate);
        this.image.setAttributeNS(null,'y', yCoordinate);
    };
    imageObj[index].src = imageSrcs[index];
}

function initAnimation(ev) {
    // Triggered on mouseover/-out
    // Change current element and init animation
    defineCurrentElement(ev.target);
    mouseEvent = ev.type;
    requestAnimationFrame(animate);
}

function animate() {
    if(mouseEvent == 'mouseover') {
        // Increase mask radius on mouseover and repeat until target state is reached

        maskRadius += Math.round(Math.max(((maxMaskRadius-maskRadius)/ease) * speed, 0.5));

        if(maskRadius >= maxMaskRadius) {
            // Target radius has been reached
            maskRadius = maxMaskRadius;
        } else {
            // Target radius hasn't been reached yet -> repeat animation
            mask.setAttributeNS(null,'r', maskRadius);
            requestAnimationFrame(animate);
        }
    } else {
        // Decrease mask radius on mouseover and repeat until initial state is reached

        maskRadius -= Math.max(((maskRadius-initialMaskRadius)/ease) * speed, 0.5);

        if(maskRadius <= initialMaskRadius) {
            // Target radius has been reached
            maskRadius = initialMaskRadius;
        } else {
            // Target radius hasn't been reached yet -> repeat animation
            mask.setAttributeNS(null,'r', maskRadius);
            requestAnimationFrame(animate);
        }
    }
}

function positionMask() {
    // Center mask inside element
    maskRadius = initialMaskRadius = parseInt(mask.getAttributeNS(null, 'r'), 10);

    var maskWidth = maskRadius * 2,
        xCoordinate = (svgWidth - maskWidth) / 2 + maskRadius,
        yCoordinate = (svgHeight - maskWidth) / 2 + maskRadius;

    mask.setAttributeNS(null,'cx', xCoordinate);
    mask.setAttributeNS(null,'cy', yCoordinate);
}
¿Fue útil?

Solución

Okke, I fixed all your code, and that was not an easy task to work with your code. Please declare all variables which you use, and not use in function global variables as your own private variables, because you can re-write existing global variable.

Now about fixed code:

  • CSS: no changes.
  • HTML: removed inline handlers (onmouseover and onmouseout)

  • Javascript:

    • When document is ready each svg element with class dday is initializing: downloading image for that svg if variable index exists (loading logic didn't changed almost), then centering mask, declaring function for animation, then adding handler for rect element in a element in initializing svg element.
    • All settings has been exported to variable settings
    • All private variables for svg is stored in {svgElement}.svgData object.

Demo: jsFiddle

P.S. By the way, this code is also not good enougt, need more time for clean code, but this code works.

HTML:

<svg class="dday sector-sports normal" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-image="http://img1.wikia.nocookie.net/__cb20130511205806/epicrapbattlesofhistory/images/9/94/Vaderrotj.jpg">

    <image height="196" width="250" />

    <a class="overlay" xlink:href="/svg/index.html" target="_top">
        <rect x="0" y="0" width="288" height="288" style="mask: url(#mask1)" />
    </a>

    <mask id="mask1">
        <rect x="0" y="0" width="288" height="288" fill="#fff" />
        <circle cx="125" cy="125" r="25" />
    </mask>

</svg>


<svg class="dday sector-sports normal" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-image="http://static.comicvine.com/uploads/original/11111/111116692/3213841-7948839370-yoda..jpg">

    <image height="196" width="250" />

    <a class="overlay" xlink:href="/svg/index.html" target="_top">
        <rect x="0" y="0" width="288" height="288" style="mask: url(#mask2)" />
    </a>

    <mask id="mask2">
        <rect x="0" y="0" width="288" height="288" fill="#fff" />
        <circle cx="125" cy="125" r="25" />
    </mask>

</svg>

Javascript: (used jQuery 1.11 library)

$(document).ready(function () {
    var settings = {
        imageWidthMax: 250,
        imageHeightMax: 196,
        ease: 50,
        speed: 12,
        maskRadiusMax: 100
    };
    var maskElements = [];

    $('svg.dday').each(function (index) {

        if (maskElements.indexOf(this) < 0) {
            maskElements.push(this);
            var sd = {};
            this.svgData = sd;

            sd.svg = $(this);
            sd.svgWidth = sd.svg.width();
            sd.svgHeight = sd.svg.height();
            sd.mask = sd.svg.find('circle')[0];

            // On page load there is a index provided to load the images for each element
            if (typeof index !== 'undefined') {

                var img = new Image();

                img.image = $('image')[index];
                img.onload = function () {
                    var m_scale_width = settings.imageWidthMax / this.width;
                    var m_scale_height = settings.imageHeightMax / this.height;
                    var m_scale = Math.min(m_scale_width, m_scale_height);

                    sd.imgWidth = this.width * m_scale;
                    sd.imgHeight = this.height * m_scale;

                    var m_x = (sd.svgWidth - sd.imgWidth) / 2;
                    var m_y = (sd.svgHeight - sd.imgHeight) / 2;

                    this.image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', sd.svg.data('image'));
                    this.image.setAttributeNS(null, 'width', sd.imgWidth);
                    this.image.setAttributeNS(null, 'height', sd.imgHeight);
                    this.image.setAttributeNS(null, 'x', m_x);
                    this.image.setAttributeNS(null, 'y', m_y);
                };
                img.src = sd.svg.data('image');

            }

            //Center mask inside element
            sd.maskRadiusInit = parseInt(sd.mask.getAttributeNS(null, 'r'), 10);
            sd.maskRadius = sd.maskRadiusInit;

            sd.maskWidth = sd.maskRadius * 2;
            sd.maskX = (sd.svgWidth - sd.maskWidth) / 2 + sd.maskRadius;
            sd.maskY = (sd.svgHeight - sd.maskWidth) / 2 + sd.maskRadius;

            sd.mask.setAttributeNS(null, 'cx', sd.maskX);
            sd.mask.setAttributeNS(null, 'cy', sd.maskY);

            var animate = function () {
                var m_addToRadius = Math.round(Math.max(((settings.maskRadiusMax - sd.maskRadius) / settings.ease) * settings.speed, 0.5));
                if (sd.eventType === 'mouseover') {
                    sd.maskRadius += m_addToRadius;

                    if (sd.maskRadius > settings.maskRadiusMax) {
                        sd.maskRadius = settings.maskRadiusMax;
                        sd.mask.setAttributeNS(null, 'r', sd.maskRadius);
                    } else {
                        sd.mask.setAttributeNS(null, 'r', sd.maskRadius);
                        requestAnimationFrame(animate);
                    }
                } else {
                    sd.maskRadius -= Math.round(Math.max(m_addToRadius, 0.5));

                    if (sd.maskRadius <= sd.maskRadiusInit) {
                        sd.maskRadius = sd.maskRadiusInit;
                        sd.mask.setAttributeNS(null, 'r', sd.maskRadius);
                    } else {
                        sd.mask.setAttributeNS(null, 'r', sd.maskRadius);
                        requestAnimationFrame(animate);
                    }
                }
            };

            $('a>rect', this).on('mouseover mouseleave', function (evt) {
                sd.eventType = evt.type;
                requestAnimationFrame(animate);
            });
        }
    });
});

Demo: jsFiddle

Otros consejos

Below is an example of a Javascript animate object, AnimateJS It works cross-browser. The example shows a quadratic hover effect for either an svg element or elements contained ing a <g>. Give it a try with your app.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Example: Hover Over Element - Quadratic</title>
  <script type="text/javascript" src="../bowser.js"></script>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body style='padding:10px;font-family:arial'>
<center>
<h4>Example: Hover Over Element - Quadratic</h4>
<div style='width:90%;background-color:gainsboro;text-align:justify;padding:10px;border-radius:6px;'>
When the cursor moves over the element its size is increased. This works on both transfomed and non-transformed elements, contained in a &lt;g&gt;, or as individual elements. Uses <b>getBBox</b> to determine scale reference point.
The previous hover size increase is reduced to its original size.
</div>
<table><tr>
<td>
<table>
<tr><td colspan=2><b>Animation Settings:</b></td></tr>
<tr><td>1. Smoothness</td><td>100 frames per second</td></tr>
<tr><td>2. Duration</td><td>200 - runtime in ms</td></tr>
<tr><td>3. Range</td><td> increase scale .5</td></tr>
<tr><td>4. Output Equation</td><td><span style=color:blue>function</span> quad(p){return Math.pow(p, 2)}</td></tr>
<tr><td>6. Application Output </td><td>element transform</td></tr>
</table><br />
<i>There are 2 hover functions: <b>hoverOverG(evt)</b> for &lt;g&gt; elements,<br />and <b>hoverOverE(evt)</b> for individual elements.</i>
</td>
<td>
<div id="svgDiv" style='background-color:lightgreen;'>
<svg version="1.1" id="mySVG" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400" overflow="hidden" >
<g id="CircleStar1" onmouseover="hoverOverG(evt)"   transform="translate(100 100)" ><polygon fill="crimson" stroke="none" points="15,-1.37091e-006 14.2658,-4.63527 12.1353,-8.81679 8.81679,-12.1353 4.63524,-14.2659 -1.37091e-006,-15 -4.63527,-14.2659 -8.81679,-12.1353 -12.1353,-8.81679 -14.2659,-4.63527 -15,-1.37091e-006 -14.2659,4.63524 -12.1353,8.81679 -8.81679,12.1353 -4.63527,14.2658 -1.37091e-006,15 4.63524,14.2658 8.81679,12.1353 12.1353,8.81679 14.2658,4.63524" /><polygon fill="dodgerblue" stroke="none" points="6.2319,3.59799 14.392,-1.37091e-006 6.2319,-3.59799 7.19598,-12.4638 -1.37091e-006,-7.19598 -7.19598,-12.4638 -6.2319,-3.59799 -14.392,-1.37091e-006 -6.2319,3.59799 -7.19598,12.4638 -1.37091e-006,7.19598 7.19598,12.4638" /></g>
<g id="CircleStar2" onmouseover="hoverOverG(evt)"   transform="translate(200 200)" ><polygon fill="crimson" stroke="none" points="15,-1.37091e-006 14.2658,-4.63527 12.1353,-8.81679 8.81679,-12.1353 4.63524,-14.2659 -1.37091e-006,-15 -4.63527,-14.2659 -8.81679,-12.1353 -12.1353,-8.81679 -14.2659,-4.63527 -15,-1.37091e-006 -14.2659,4.63524 -12.1353,8.81679 -8.81679,12.1353 -4.63527,14.2658 -1.37091e-006,15 4.63524,14.2658 8.81679,12.1353 12.1353,8.81679 14.2658,4.63524" /><polygon fill="dodgerblue" stroke="none" points="6.2319,3.59799 14.392,-1.37091e-006 6.2319,-3.59799 7.19598,-12.4638 -1.37091e-006,-7.19598 -7.19598,-12.4638 -6.2319,-3.59799 -14.392,-1.37091e-006 -6.2319,3.59799 -7.19598,12.4638 -1.37091e-006,7.19598 7.19598,12.4638" /></g>
<g id="CircleStar3" onmouseover="hoverOverG(evt)"   transform="translate(300 300)" ><polygon fill="crimson" stroke="none" points="15,-1.37091e-006 14.2658,-4.63527 12.1353,-8.81679 8.81679,-12.1353 4.63524,-14.2659 -1.37091e-006,-15 -4.63527,-14.2659 -8.81679,-12.1353 -12.1353,-8.81679 -14.2659,-4.63527 -15,-1.37091e-006 -14.2659,4.63524 -12.1353,8.81679 -8.81679,12.1353 -4.63527,14.2658 -1.37091e-006,15 4.63524,14.2658 8.81679,12.1353 12.1353,8.81679 14.2658,4.63524" /><polygon fill="dodgerblue" stroke="none" points="6.2319,3.59799 14.392,-1.37091e-006 6.2319,-3.59799 7.19598,-12.4638 -1.37091e-006,-7.19598 -7.19598,-12.4638 -6.2319,-3.59799 -14.392,-1.37091e-006 -6.2319,3.59799 -7.19598,12.4638 -1.37091e-006,7.19598 7.19598,12.4638" /></g>
<g id=rectEllipse transform="translate(330 20)scale(.5)" onmouseover="hoverOverG(evt)">
<rect x=50 y=200 width=60 height=50 fill=orange />
<ellipse cx=80 cy=227 rx=25 ry=15 fill=blue />
</g>
<g id=rectEllipseTransform transform="translate(130 120)scale(.5)" onmouseover="hoverOverG(evt)">
<rect x=50 y=200 width=60 height=50 fill=orange />
<ellipse cx=80 cy=227 rx=25 ry=15 fill=blue />
</g>
<g id=hoverElements >
<circle onmouseover="hoverOverE(evt)" cx=250 cy=150 r=10 fill=blue />
<circle onmouseover="hoverOverE(evt)" cx=150 cy=150 r=10 fill=blue />
<circle onmouseover="hoverOverE(evt)" cx=350 cy=350 r=10 fill=blue />
<circle transform="translate(110 40)" onmouseover="hoverOverE(evt)" cx=150 cy=150 r=10 fill=maroon />
<circle transform="translate(220 80)" onmouseover="hoverOverE(evt)" cx=150 cy=150 r=10 fill=maroon />
<circle transform="translate(220 80)" onmouseover="hoverOverE(evt)" cx=-10 cy=-10 r=10 fill=red />
<circle transform="translate(80 320)scale(.8)" onmouseover="hoverOverE(evt)" cx=-10 cy=-10 r=10 fill=red />
</g>
</svg>
</div>

</td>
</tr> </table>
  <br />SVG Source:<br />
<textarea id=svgSourceValue style='font-size:110%;font-family:lucida console;width:90%;height:200px'></textarea>
  <br />Javascript:<br />
<textarea id=jsValue style='border-radius:26px;font-size:110%;font-weight:bold;color:midnightblue;padding:16px;background-color:beige;border-width:0px;font-size:100%;font-family:lucida console;width:90%;height:400px'></textarea>
</center>
<div id='browserDiv' style='padding:5px;position:absolute;top:5px;left:5px;background-color:gainsboro;'>OK in:IE11/CH32/FF23<br /></div>
<script id=myScript>
/*---generalized animate core function
Allows progress/output to follow a specific/customized equation(delta)
Inspired by: Ilya Kantor - http://javascript.info/tutorial/animation
*/
var AnimateJS=function(options){
    this.options=options
    var start = new Date
    var iT = setInterval(
    function(){
        var timePassed = new Date - start
        var progress = timePassed / options.duration
        if (progress > 1) progress = 1
        this.progress=progress
        var delta = options.delta(progress)
        options.output(delta)
        if (progress == 1)clearInterval(iT);
    },options.delay)
}
/*
provide options:
1) range(end value)
2) frames per second(delay = 1000/frames per second)
3) duration in ms
4) delta: equation(linear,etc.)
5) output:  This application's output function
*/
var HoverSizeIncrease=.5 //---the element's size increased by 50%--
var FinishedOver=true
var StartTrans=null
var PrevTarget=null
//--onmouseover g symbol---
function hoverOverG(evt)
{
    if(FinishedOver==true && (evt.target.parentNode!=PrevTarget)) //--allows initial run---
    {
        if(PrevTarget)
            extractHover(PrevTarget)
        var target=evt.target.parentNode
        PrevTarget=target
        FinishedOver=false
        var scaleBegin=1
        var range=HoverSizeIncrease //---scale increase
        var FPS=100  //---frames per second---
        var duration=200 //---ms,.2 seconds---
        //---quadratic formula in nth degree---
        var delta=function quad(p){return Math.pow(p,2)}
        if(target.getAttribute("transform"))
        {
            StartTrans=target.getAttribute("transform")
            var myTrans=StartTrans
        }
        else
        {
            StartTrans=null
            var myTrans=""
        }
        var bb=target.getBBox()
        var bbx=bb.x
        var bby=bb.y
        var bbw=bb.width
        var bbh=bb.height
        var cx=bbx+.5*bbw
        var cy=bby+.5*bbh
        //----core animation function---
        new AnimateJS(
        {
            delay: 1000/FPS,
            duration: duration,
            delta: delta, //---quadratic---
            output: function(delta)
            {
                var scale=scaleBegin+delta*range
                target.setAttribute("transform",myTrans+"translate("+(cx)+" "+(cy)+")scale("+scale+")translate("+(-cx)+" "+(-cy)+")")
                //---finished---
                if(progress==1)
                    onFinish()
            }
        })
    }
}
//--onmouseover element---
function hoverOverE(evt)
{
    if(FinishedOver==true && (evt.target!=PrevTarget)) //--allows initial run---
    {
        if(PrevTarget)
            extractHover(PrevTarget)
        var target=evt.target
        PrevTarget=target
        FinishedOver=false
        var scaleBegin=1
        var range=HoverSizeIncrease //---scale increase
        var FPS=100  //---frames per second---
        var duration=200 //---ms,.2 seconds---
        //---quadratic formula in nth degree---
        var delta=function quad(p){return Math.pow(p,2)}
        if(target.getAttribute("transform"))
        {
            StartTrans=target.getAttribute("transform")
            var myTrans=StartTrans
        }
        else
        {
            StartTrans=null
            var myTrans=""
        }

        var bb=target.getBBox()
        var bbx=bb.x
        var bby=bb.y
        var bbw=bb.width
        var bbh=bb.height
        var cx=bbx+.5*bbw
        var cy=bby+.5*bbh
        //----core animation function---
        new AnimateJS(
        {
            delay: 1000/FPS,
            duration: duration,
            delta: delta, //---quadratic---
            output: function(delta)
            {
                var scale=scaleBegin+delta*range
                target.setAttribute("transform",myTrans+"translate("+(cx)+" "+(cy)+")scale("+scale+")translate("+(-cx)+" "+(-cy)+")")
                //---finished---
                if(progress==1)
                    onFinish()
            }
        })
    }
}
var FinishedExtract=true
var ExtractTarget
var ExtractTrans
function extractHover(PrevTarget)
{
    if(FinishedExtract==true) //--allows initial run---
    {
        ExtractTarget=PrevTarget
        if(StartTrans)
            ExtractTrans=StartTrans
        else
            ExtractTrans=""
        FinishedExtract=false
        var scaleBegin=1+HoverSizeIncrease
        var range=HoverSizeIncrease //---scale decrease
        var FPS=100  //---frames per second---
        var duration=200 //---ms,.2 seconds---
        //---quadratic formula in nth degree---
        var delta=function quad(p){return Math.pow(p,2)}
        var bb=ExtractTarget.getBBox()
        var bbx=bb.x
        var bby=bb.y
        var bbw=bb.width
        var bbh=bb.height
        var cx=bbx+.5*bbw
        var cy=bby+.5*bbh

        //----core animation function---
        new AnimateJS(
        {
            delay: 1000/FPS,
            duration: duration,
            delta: delta, //---quadratic---
            output: function(delta)
            {
                var scale=scaleBegin-delta*range
                ExtractTarget.setAttribute("transform",ExtractTrans+"translate("+(cx)+" "+(cy)+")scale("+scale+")translate("+(-cx)+" "+(-cy)+")")

                    if (progress == 1) // --- finished---
                        extractFinish();
            }
        })
    }
}

//---this example animation: loop finished---
function onFinish()
{
    FinishedOver=true

}

//---this example animation: loop finished---
function extractFinish()
{
    FinishedExtract=true
    if(ExtractTrans!="")
        ExtractTarget.setAttribute("transform",ExtractTrans)
    else
        ExtractTarget.removeAttribute("transform")
}


</script>
<script>
document.addEventListener("onload",init(),false)
function init()
{
    jsValue.value=myScript.text
    svgSourceValue.value=svgDiv.innerHTML
}

</script>

</body>

</html>

Try to do this using very simple way. I've not applied any animation. I think you'll figure out how to do that.

Remove mouse event from inline

JS

$('svg')
  .mouseenter(function(ev) {
       console.log($(this).find('circle').attr('r',40));
  })
  .mouseleave(function(ev) {
      console.log($(this).find('circle').attr('r',25));

});

CHECK JSFiddle

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top