Kathryn Crawford
Kathryn Crawford

Reputation: 611

Detect which element is moused over and pass to function

I'm using greensock to animate an svg clippath, and it works great with one clippath, and hardcoded variables. Now I'm needing to add more clippaths, and I need each one to animate independently. So I need to build some sort of function to detect which circle is being moused over/ moused out and then call the timeline, passing it the correct parameters (the clippath and overlay circle). I'm sure I could probably do that with 'this' but I'm still at the point where 'this' makes my brain melt. Here's the codepen I'm working on.

http://codepen.io/kathryncrawford/pen/JYvdzx

HTML

  <svg class="svg-graphic" width="500" height="500" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" version="1.1">
<defs>
  <clipPath id="clippath">
  <circle id="clip1" cx="200" cy="200" r="2.5"/>
  <circle id="clip2" cx="400" cy="200" r="3.2"/>  
  </clipPath>
</defs>
<image class="svg-image1" xlink:href="http://lorempixel.com/300/300/" width="300" height="300" x="80" y="80"/>
<circle id="circle1"  fill="#CC66FF" cx="200" cy="200" r="30"/>

<image class="svg-image2" xlink:href="http://lorempixel.com/300/300/" width="300" height="300" x="380" y="80"/>
<circle id="circle2" fill="#CC66FF" cx="400" cy="200" r="30"/>

JS

var clip = document.getElementById("clip1");
var circles = document.getElementById("circle1");

circles.addEventListener("mouseenter", expand);
circles.addEventListener("mouseleave", contract);

var tl = new TimelineMax({paused: true});
tl.to(clip, 0.5, {
    attr: {
      r: 120
    },
    transformOrigin: "50% 50%",
    ease: Power4.easeInOut
  })
  .to(circles, 0.5, {alpha:0, ease:Power4.easeInOut}, '-0.1');

function expand() {
  tl.play();
}

function contract() {
  tl.reverse();
}

Upvotes: 0

Views: 97

Answers (1)

Tahir Ahmed
Tahir Ahmed

Reputation: 5737

All right, this is what I was able to create by forking your pen.

And here is what has changed:

  • In HTML, I have removed unique IDs set on each of the circle HTML elements present inside the clipPath HTML element i.e. clipPath's children. Instead, I have given all of these circle tags a clip class.
  • The other circle elements that are siblings of the said clipPath i.e. present on the same level as clipPath element, have been given a circle class.
  • And as for the image elements, I have done similar thing. Removed unique IDs from them and instead gave them a common svg-image class.
  • This is HTML done.
  • In HTML, since the unique IDs have been removed such as #circle1, #circle2, #svg-image1 and #svg-image2, I have removed them from CSS as well and instead applied exactly the same rules they had on the newly created classes i.e. .circle and .svg-image respectively.
  • In JavaScript, the clip and circle elements as well as the total number of clip elements are first stored in variables clips, circles and numClips respectively.
  • There is also an empty array of timelines created initially.
  • Then there is a loop being initiated which goes up till the length of numClips and which does two things:
    • createTimeline as the name suggests, is supposed to create a TimelineMax instance which looks similar to what you previously had i.e. it adds two tweens, one for animating the opacity on the current circle element (remember, we are inside a loop and we have a reference of current circle element by the use of circles[i]) and the other for animating r of the current clip element.
    • assignListeners is for listening to mouseenter and mouseleave events on each of the circle elements.
  • And finally, expand and collapse methods are for playing or reversing the current timeline instance. (again, we have the reference of the timeline that should be playing when hovered or out using timelines[i] reference).

HTML:

<svg class="svg-graphic" width="500" height="500" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" version="1.1">
  <defs>
    <clipPath id="clippath">
      <circle class="clip" cx="200" cy="200" r="20" />
      <circle class="clip" cx="400" cy="200" r="20" />
      <circle class="clip" cx="600" cy="200" r="20" />
    </clipPath>
  </defs>
  <image class="svg-image" xlink:href="http://lorempixel.com/300/300/" width="300" height="300" x="80" y="80" />
  <circle class="circle" fill="#CC66FF" cx="200" cy="200" r="20" />
  <image class="svg-image" xlink:href="http://lorempixel.com/300/300/" width="300" height="300" x="380" y="80" />
  <circle class="circle" fill="#CC66FF" cx="400" cy="200" r="20" />
  <image class="svg-image" xlink:href="http://lorempixel.com/300/300/" width="300" height="300" x="680" y="80" />
  <circle class="circle" fill="#CC66FF" cx="600" cy="200" r="20" />
</svg>

CSS:

*{
    box-sizing: border-box;
}

body{
    margin: 0;
    padding: 0  
}

.circle{        
  position: absolute;
  margin: 0;
  z-index: 1;
  clip-path: url("#clippath");
}

.svg-image {
  z-index: 3;
  clip-path: url(#clippath);
}

svg{
    overflow: visible;
}

.svg-graphic {
  position: absolute;
}

.imgContainer {
  position: relative;
  width: 800px;
  height: 800px;
}

JavaScript:

var clips = document.getElementsByClassName('clip');
var circles = document.getElementsByClassName('circle');
var numClips = clips.length;
var timelines = [];

for (var i = 0; i < numClips; i += 1) {
  createTimeline(i);
  assignListeners(i);
}

function createTimeline(i) {
  var timeline = new TimelineMax({ paused: true });
  timeline.to(circles[i], 0.6, { opacity: 0, ease: Expo.easeInOut }, 0);
  timeline.to(clips[i], 0.6, {
    attr: { r: 120 },
    transformOrigin: '50% 50%',
    ease: Expo.easeInOut
  }, 0.1);
  timelines[i] = timeline;
}

function assignListeners(i) {
  (function(i) {
    circles[i].addEventListener('mouseenter', function(e) { expand(e, i); }, false);
    circles[i].addEventListener('mouseleave', function(e) { contract(e, i); }, false);
  }(i));
}

function expand(e, i) { timelines[i].play(); }
function contract(e, i) { timelines[i].reverse(); }

Hope this helps.

Upvotes: 1

Related Questions