I have defined a path with opacity 0 that includes within several elements. The purpose of that is because when mouse is over that path, the element appears (or disappear if we left the path). My problem is since the path is on top of the canvas, I cannot click on those elements and expect to be able to handle the event.
What would be the best strategy to deal with that?
I tried to put the path on back(); but when I hover over an element, it's like if I left the path.
I tried to put the element on top, but same result as before.
Using the function ispointinsidepath() and then getting the coordinates of the point I clicked seems fastidious, since element size are not constant.
You can put handlers on each of the elements, and deal with it that way. Might even be slightly easier if using Snap rather than Raphael as you can use groups then (and not need multiple event handlers), but assuming Raphael is a requirement you could do this...
var r = paper.rect(50,50,200,200).attr({ fill: 'blue', opacity: '0'}).hover( hoverover, hoverout )
var c1 = paper.circle(100,100,30).attr({ fill: 'red' }).click( clickFunc ).hover( hoverover, hoverout)
var c2 = paper.circle(200,200,30).attr({ fill: 'blue' }).click( clickFunc ).hover( hoverover, hoverout )
function hoverover() { r.attr({ opacity: 1 } ) }
function hoverout() { r.attr({ opacity: 0 } ) }
function clickFunc() { alert('clicked')}
jsfiddle
Related
I am attempting to animate an svg doughnut circle with gsap. After much testing with code and layering, I am stumped with a glitchy hover effect (which I tried to resolve with pointer events) and the transform origin is only applied to a few of the expanded tabs. I am wondering if this might be that the tabs may have a different bounding box?
Comments added per request:
Side Note: I've tried applying fill-box to entire svg instead, I'm wondering if I need a parent layer thats an exact square so I can apply the transform origin for the child "expandtabs" to the center of that?
I assumed I needed to iterate through an array of both to have the tabs correspond. Unless the tabs were children of each other?
TLDR; Tabs are not scaling from center of circle, and glitchy hover effect
CodePen Example
.expandtab {
pointer-events: none;
transform: fill-box;
transform-origin: -15px 25%;
}
Javascript:
const subTabs = gsap.utils.toArray(".subtab");
const expandTabs = gsap.utils.toArray(".expandtab");
const tl = gsap.timeline({ defaults: { duration: .05, } });
tl.set(expandTabs, {
visibility: "hidden",
opacity: 0,
scale: 0,
});
subTabs.forEach((subTab, index) => {
let expandTab = expandTabs[index];
// Event Listener Hover on
subTabs[index].addEventListener("mouseover", (event) => {
console.log("you clicked region number " + index);
tl.to(expandTab, {
visibility: "visible",
opacity: 1,
scale: 1,
});
});
// Event Listener Hover off
subTabs[index].addEventListener("mouseout", (event) => {
console.log("you exited region number " + index);
tl.to(expandTab, {
opacity: 0,
scale: 0,
visibility: "hidden",
});
});
});
About the glitchy hover effect, the mouseenter and mouseleave will do the job better. mouseover is firering way to much...
For the "growing" effect, it is more complex. The transform-origin CSS property won't be enought. Any way, you will need different values for each five parts of the circle.
Additionnaly, you will need to adjust a transition to "fit" or "keep" the inner part of the circle in place. I suggest you to look at the fromTo method of GSAP. That will allow you to specify explicitely the starting and the landing coordinates.
Be patient! ;)
I would like to add category icons to a Wordpress page, each icon animated with snap.svg.
I added the div and inside an svg in the loop that prints the page (index.php). All divs are appearing with the right size of the svg, but blank.
The svg has a class that is targeted by the js file.
The js file is loaded and works fine by itself, but the animation appears only in the first div of that class, printed on each other as many times it is counted by the loop (how many posts there are on the actual page from that category).
I added "each()" and the beginning of the js, but is not allocating the animations on their proper places. I also tried to add double "each()" for the svg location and adding the snap object to svg too, but that was not working either.
I tried to add unique id to each svg with the post-id, but i could not pass the id from inside the loop to the js file. I went through many possible solutions I found here and else, but none were adaptable, because my php and js is too poor.
If you know how should I solve this, please answer me. Thank you!
// This is the js code (a little trimmed, because the path is long with many randoms, but everything else is there):
jQuery(document).ready(function(){
jQuery(".d-icon").each(function() {
var dicon = Snap(".d-icon");
var dfirepath = dicon.path("M250 377 C"+ ......+ z").attr({ id: "dfirepath", class: "dfire", fill: "none", });
function animpath(){ dfirepath.animate({ 'd':"M250 377 C"+(Math.floor(Math.random() * 20 + 271))+ .....+ z" }, 200, mina.linear);};
function setIntervalX(callback, delay, repetitions, complete) { var x = 0; var intervalID = window.setInterval(function () { callback(); if (++x === repetitions) { window.clearInterval(intervalID); complete();} }, delay); }
var dman = dicon.path("m136 ..... 0z").attr({ id: "dman", class:"dman", fill: "#222", transform: "r70", });
var dslip = dicon.path("m307 ..... 0z").attr({ id: "dslip", class:"dslip", fill: "#196ff1", transform:"s0 0"});
var dani1 = function() { dslip.animate({ transform: "s1 1"}, 500, dani2); }
var dani2 = function() { dman.animate({ transform: 'r0 ' + dman.getBBox().cx + ' ' + dman.getBBox(0).cy, opacity:"1" }, 500, dani3 ); }
var dani3 = function() { dslip.animate({ transform: "s0 0"}, 300); dman.animate({ transform: "s0 0"}, 300, dani4); }
var dani4 = function() { dfirepath.animate({fill: "#d62a2a"}, 30, dani5); }
var dani5 = function() { setIntervalX(animpath, 200, 10, dani6); }
var dani6 = function() { dfirepath.animate({fill: "#fff"}, 30); dman.animate({ transform: "s1 1"}, 100); }
dani1(); }); });
I guess your error is here:
var dicon = Snap(".d-icon");
You are passing a query selector to the Snap constructor, this means Snap always tries to get the first DOM element with that class, hence why you're getting the animations at the wrong place.
You can either correct that in two ways:
Declare width and height inside the constructor, for example var dicon = Snap(800, 600);
Since you are using jQuery you can access to the current element inside .each() with the $(this) keyword. Since you are using jQuery instead of the dollar you could use jQuery(this).
Please keep in mind this is a jQuery object and probably Snap will require a DOM object. In jQuery you can access the dom object by appending a [0] after the this keyword. If var dicon = Snap( jQuery(this) ); does not work you can try with var dicon = Snap( jQuery(this)[0] );
Additionally, you have several .attr({id : '...', in your code. I assume you are trying to associate to the paths an ID which are not unique. These should be relatively safe since they sit inside a SVG element and I don't see you are using those ID for future selection.
But if you have to select those at a later time I would suggest to append to these a numerical value so you wont have colliding ID names.
I've created several shapes in SVG and added an effect that changes the shapes color when hovered over.
But how can I make the shapes "pop up" slightly when hovered over?
Would appreciate any help.
You can modify the transform of each element. Probably something like this:
function onEnter (e) {
e.target.setAttribute('transform', 'scale(2,2)');
}
function onLeave (e) {
e.target.removeAttribute('transform');
}
But in fact you do not need to take the long slow way using the attributes, instead you can use the transform interface of SVG elements. The documentation therefore is on the page linked above, too. But dealing with this might be a bit »unintuitive« the first time, since each element has a transform list with several transform items.
I think it would be a good Idea to dive in the svg transform interface (and affine transformations in general), since it is very powerful, but it requires a »learning curve« before you are ready to use it.
If you want to short circuit this you can also use a helper framework like: svg.js, snapsvg or raphael which all have common methods to help you dealing with thing like that.
EDIT
The functions above are just examples for one could do it. The onEnter and onLeave methods should be called only if the mouse enters the svg element or when it leaves. So you could add a mousemove listener and check if the target is one of your desired elements:
var firstElement = document.getElementById('<yourelementsid>');
isOverFirstElement = false;
window.addEventListener('mousemove', function (e) {
if (e.target === firstElement && isOverFirstElement == false) {
isOverFirstElement = true;
onEnter(e);
} else if (e.target !== firstElement && isOverFirstElement == true) {
isOverFirstElement = false;
onLeave(e);
}
}, false);
With code like that you can detect a mouse enter or leave and react accordingly.
I've got it to do what I wanted by adding
target.setAttributeNS(null, 'transform', "translate(0 0)");
as such:
function unselectedColour(evt) {
var target = evt.target;
target.setAttributeNS(null, 'fill', 'white');
target.setAttributeNS(null, 'transform', "translate(0 0)");
}
function selectedColourBuilding(evt) {
var target = evt.target;
target.setAttributeNS(null, 'fill', 'purple');
target.setAttributeNS(null, 'transform', "translate(-3 -3)");
}
To make the 'pop out' stable, keeping it at it's current position when scaling, you can compute the center of its bounding box then translate the element's center to the origin, scale it, then translate back to its current location
e.g.
function onEnter(evt)
{
var target=evt.target
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
target.setAttribute("transform","translate("+(cx)+" "+(cy)+")scale(1.2 1.2)translate("+(-cx)+" "+(-cy)+")")
}
function onExit(evt)
{
var target=evt.target
target.removeAttribute("transform")
}
I ran across this snippet of code and have "used" it as a reference for developing my own specific toggle function.
Raphael.js - if / else statement not toggling on click
http://jsfiddle.net/YLrzk/1/
I would like to apply an animation to the stroke-width per say when it is increased on click. I can't seem to figure out how to add this animation in alongside the toggle function.
I figured this would be applied around the variables StrON and StrOFF so I have tried things such as:
var strOff = function() {
this.animate({ 'stroke-width': '1' }, 100);
};
var strOn = function() {
this.animate({ 'stroke-width': '5' }, 100);
};
and even just:
var strOff =
this.animate({ 'stroke-width': '1' }, 100);
var strOn =
this.animate({ 'stroke-width': '5' }, 100);
Sorry about the lazy syntax If I missed anything on the two examples of what I've tried. Thanks for any help.
Neither of these will work because strOn and strOff are not the right data type -- they must be an object containing attributes for the selected and deselected states of a given rectangle. This represents a fundamental misunderstanding of what animate does: it is essentially an asynchronous version of attr.
You could solve your problem by simply restoring strOn and strOff to their original state and then calling this in the click handler for a given rectangle:
box1.animate( strOn, 100 );
box2.animate( strOff, 100 );
box3.animate( strOff, 100 );
This still leaves you with a complexity issue. If you want to add a fourth or fifth rectangle, you will quickly drown in conditional logic. This sort of state information should, in my opinion, almost never be implemented like this. Instead, I recommend doing this:
Use a single, generic click handler.
var genericClickHandler = function()
{
// First step: find and deselect the currently "active" rectangle
paper.forEach( function( el )
{
if ( el.data('box-sel' ) == 1 )
{
el.animate( strOff, 100 ).data('box-sel', 0 );
return false; // stops iteration
}
} );
this.animate( strOn, 100 ).data( 'box-sel', 1 );
}
This will iterate through all elements in the paper -- if one of them is marked as "active," it will be animated back into its inactive state.
Use data to keep track of the selected rectangle:
paper.rect( x1, y1, w1, h1 ).attr( {} ).data( 'box-sel', 0 ).click( genericClickHandler ); // note that we're setting data to indicate that this rectangle isn't "active"
paper.rect( x2, y2, w2, h2 ).attr( {} ).data( 'box-sel', 0 ).click( genericClickHandler );
// ... as many rectangles as you like
paper.rect( xN, yN, wN, hN ).attr( {} ).data( 'box-sel', 0 ).click( genericClickHandler );
Using this approach, there's no need to keep track of individual rectangles -- only whether or not a given rectangle is selected or not.
Here's an example supporting many rectangles.
I'm trying to make a page inspection tool, where:
The whole page is shaded
Hovered elements are unshaded.
Unlike a lightbox type app (which is similar), the hovered items should remain in place and (ideally) not be duplicated.
Originally, looking at the image lightbox implementations, I thought of appending an overlay to the document, then raising the z-index of elements upon hover. However this technique does not work in this case, as the overlay blocks additional mouse hovers:
$(function() {
window.alert('started');
$('<div id="overlay" />').hide().appendTo('body').fadeIn('slow');
$("p").hover(
function () {
$(this).css( {"z-index":5} );
},
function () {
$(this).css( {"z-index":0} );
}
);
Alternatively, JQueryTools has an 'expose' and 'mask' tool, which I have tried with the code below:
$(function() {
$("a").click(function() {
alert("Hello world!");
});
// Mask whole page
$(document).mask("#222");
// Mask and expose on however / unhover
$("p").hover(
function () {
$(this).expose();
},
function () {
$(this).mask();
}
);
});
Hovering does not work unless I disable the initial page masking. Any thoughts of how best to achieve this, with plain JQuery, JQuery tools expose, or some other technique? Thankyou!
What you can do is make a copy of the element and insert it back into the DOM outside of your overlay (with a higher z-index). You'll need to calculate its position to do so, but that's not too difficult.
Here is a working example.
In writing this I re-learned the fact that something with zero opacity cannot trigger an event. Therefore you can't use .fade(), you have to specifically set the opacity to a non-zero but very small number.
$(document).ready(function() { init() })
function init() {
$('.overlay').show()
$('.available').each(function() {
var newDiv = $('<div>').appendTo('body');
var myPos = $(this).position()
newDiv.addClass('available')
newDiv.addClass('peek')
newDiv.addClass('demoBorder')
newDiv.css('top',myPos.top+'px')
newDiv.css('left',myPos.left+'px')
newDiv.css('height',$(this).height()+'px')
newDiv.css('width',$(this).width()+'px')
newDiv.hover(function()
{newDiv.addClass('full');newDiv.stop();newDiv.fadeTo('fast',.9)},function()
{newDiv.removeClass('full');newDiv.fadeTo('fast',.1)})
})
}
Sorry for the prototype syntax, but this might give you a good idea.
function overlay() {
var div = document.createElement('div');
div.setStyle({
position: "absolute",
left: "0px",
right: "0px",
top: "0px",
bottom: "0px",
backgroundColor: "#000000",
opacity: "0.2",
zIndex: "20"
})
div.setAttribute('id','over');
$('body').insert(div);
}
$(document).observe('mousemove', function(e) {
var left = e.clientX,
top = e.clientY,
ele = document.elementFromPoint(left,top);
//from here you can create that empty div and insert this element in there
})
overlay();