Web Animations API — how to play backwards rather than reverse? - animation

The Web Animations API has the reverse method which allows the running animation to be played back in the opposite direction it is currently headed.
However I have a scenario where I would just like to play a defined animation backwards. At present, I am accomplishing this by checking if the animation is going forwards or backwards first and then doing the opposite but this feels wonky!
Is there an established pattern for just asking a defined animation to run backwards (and not necessarily run in reverse from its current playhead)?
In code example below, if you are on the first instance (idx === 0) I check if you are going forwards by checking if playbackRate is 1 first. Without this, the animation would run backwards first.
Feels like there should be a simpler way of running an animation backwards when required — is there?
var box = document.querySelector(".thing");
var btn = document.querySelector("button");
var span = document.querySelector("span");
const move = box.animate(
[{ transform: "translate(0px, 0px)" }, { transform: "translate(100px, 0px)" }],
{ duration: 500, fill: "both" }
);
move.pause();
let idx = 0;
function choose() {
if (idx === 0) {
move.playbackRate === 1 ? move.play() : move.reverse();
} else {
move.reverse();
}
}
btn.addEventListener("click", () => {
choose();
idx = idx === 0 ? 1 : 0;
span.textContent = idx;
});
.thing {
width:49px;
height: 49px;
background-color: red;
}
<div class="thing"></div>
<button type="button">ClickMe</button>
<span>0</span>

I believe you want the direction property, e.g.
move.effect.updateTiming({ direction: 'reverse' });

Related

how to avoid multiple p5.js sketches to run all at once

I have a web page which encloses a few sketches, all written in P5.JS
Each sketch uses its own name space, so that it runs independently from the others.
I noticed that, for each sketch, the level of performance is lower than the one I get when it runs alone in a separate web page.
My question : what can I do to prevent all the sketches to run all at once ? Is it possible, for example, to activate a sketch only when the mouse hovers its canvas ? It would probably spare ressources.
Thank you for your help.
You can call noLoop() and loop() to stop and restart a sketch. There aren't any built in p5.js events to help you trigger noLoop() when the mouse leaves the sketch or when the sketch is scrolled off screen, however there are a couple of ways you can do it which rely on using the underlying browser functionality:
The built in mouseenter and mouseleave events
Checking winMouseX and winMouseY against the sketch canvas getBoundingClientRect() in each call to draw()
function makeSketch(...colorArgs) {
return (p) => {
let bgColor;
let black;
let c;
p.setup = () => {
c = p.createCanvas(p.windowWidth, p.windowHeight / 3);
bgColor = p.color(...colorArgs);
black = p.color(0);
c.elt.addEventListener('mouseenter', () => {
p.loop();
});
c.elt.addEventListener('mouseleave', () => {
p.noLoop();
});
let bounds = c.elt.getBoundingClientRect();
// Just in case the mouse is already over the canvas when it is created.
// This is also how you would use getBoundingClientRect from the draw()
// and mouseMoved() functions instead of the mouseenter/mouseleave events.
if (p.winMouseX < bounds.left ||
p.winMouseX > bounds.right ||
p.minMouseY < bounds.top ||
p.winMouseY > bounds.bottom) {
p.noLoop();
}
};
p.draw = () => {
p.background(p.lerpColor(
bgColor,
black,
p.abs((p.frameCount % 240 - 120) / 120)
));
let bounds = c.elt.getBoundingClientRect();
p.fill('white');
p.noStroke();
p.text(`${p.winMouseX}, ${p.winMouseY} :: ${bounds.left}, ${bounds.top}, ${bounds.right}, ${bounds.bottom}`, 10, 10);
}
};
}
let sketch1 = new p5(makeSketch('red'));
let sketch2 = new p5(makeSketch(0, 255, 0));
let sketch3 = new p5(makeSketch('blue'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
You might also find that it is sufficient to pause sketches that are off screen:
function makeSketch(...colorArgs) {
return (p) => {
let bgColor;
let black;
let c;
let isLooping;
p.setup = () => {
c = p.createCanvas(p.windowWidth, p.windowHeight);
bgColor = p.color(...colorArgs);
black = p.color(0);
let bounds = c.elt.getBoundingClientRect();
isLooping = true;
if (bounds.bottom < 0 ||
bounds.top > p.windowHeight) {
p.noLoop();
isLooping = false;
}
// Might need to check this on resize as well.
document.addEventListener('scroll', () => {
let bounds = c.elt.getBoundingClientRect();
// Note this only checks verticle scrolling, but you could check horizontal as well
if (bounds.bottom > 0 &&
bounds.top <= p.windowHeight) {
if (!isLooping) {
isLooping = true;
console.log(`sketch ${colorArgs.join(',')}: loop`);
p.loop();
}
} else if (isLooping) {
isLooping = false;
console.log(`sketch ${colorArgs.join(',')}: noLoop`);
p.noLoop();
}
});
};
p.draw = () => {
p.background(p.lerpColor(
bgColor,
black,
p.abs((p.frameCount % 240 - 120) / 120)
));
p.fill('white');
p.noStroke();
p.text(`${p.frameCount}`, 10, 10);
}
};
}
let sketch1 = new p5(makeSketch('red'));
let sketch2 = new p5(makeSketch(0, 255, 0));
let sketch3 = new p5(makeSketch('blue'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

Transform translate has poor performance on IE

I am currently creating a floormap using HTML and Canvas.
Here is the test url : http://test.fastcoding.jp/clem/floormap/
Map image for each zoom level is cut into pieces (with PHP), and map is rebuilt using HTML divs with different transform: translate() positions.
On mouse wheel events, a scale factor is applied to each divs, and once the scale become 1 the next zoom level is shown.
On mouse drag, translate values are updated.
So now here is the problem.
Everything works fine on Chrome, Safari, but on IE dragging actions are really laggy (a bit laggy in Firefox as well).
Here is my map refresh function that is called on drag :
app.refresh_map = function() {
var curr_map = app.maps[app.curr_zoom];
var maxI = curr_map.tiles.length-1;
var maxJ = curr_map.tiles[0].length-1;
var scale = app.zooms[app.curr_zoom][app.curr_scale];
for(var i=0; i<=maxI; i++) {
for(var j=0; j<=maxJ; j++) {
var curr_tile = curr_map.tiles[i][j];
var new_tile = {
x: curr_tile.x*scale, y: curr_tile.y*scale,
width: curr_tile.width, height: curr_tile.height
};
var selector = ".fm-tiles[data-map='"+app.mapID+"'] .fm-tile[data-zoom='"+app.curr_zoom+"'][data-tile='"+i+"-"+j+"']";
if ( app.in_screen(new_tile) ) {
if( $(selector).length==0 ) $tiles.append('<div class="fm-tile" data-zoom="'+app.curr_zoom+'" data-tile="'+i+"-"+j+'"></div>');
var $tile = $(selector);
var x = app.pos.x + new_tile.x;
var y = app.pos.y + new_tile.y;
x = parseFloat(x.toFixed(2));
y = parseFloat(y.toFixed(2));
if(!$tile.hasClass("loaded")) {
$tile.css("background", "url('api/results/"+mapID+"/"+app.curr_zoom+"/"+curr_tile.src+"') no-repeat left top");
$tile.css("background-size", "100% 100%");
$tile.addClass("loaded");
$tile.css({
"width": curr_tile.width+"px",
"height": curr_tile.height+"px"
});
}
if(app.browser=="ie") {
$tile.css({
"transform-origin": "0% 0%",
"-ms-transform": "translate("+x+"px,"+y+"px) scale("+scale+","+scale+")"
});
}
else {
$tile.css({
"transform-origin": "0px 0px 0px",
"-webkit-transform": "translate3d("+x+"px,"+y+"px, 0px) scale("+scale+","+scale+") rotate(0.01deg)",
"-moz-transform": "translate3d("+x+"px,"+y+"px, 0px) scale("+scale+","+scale+") rotate(0.01deg)",
"transform": "translate3d("+x+"px,"+y+"px, 0px) scale("+scale+","+scale+") rotate(0.01deg)"
});
}
if(!$tile.is(":visible")) {
$tile.show().addClass("shown");
}
}
else {
if($(selector).length!=0){
$(selector).hide();
}
}
}
}
$(".fm-tiles[data-map='"+app.mapID+"'] .fm-tile:not([data-zoom='"+app.curr_zoom+"'])").hide();
}
I know it is possible to make it smooth because there is this map which work perfectly on any browser : http://okayamaeki-sc.jp/floorguide/#/sunste2f
I looked a bit the code but it is ugglyfied so a bit hard to understand.
I know that it uses different transform and transform-origin according to the browser and that it is using a sort of easing, but I don't know if it's the reason why it is so smooth.
Does anyone have any clue ?
Thanks.

hammer.js detect variables in panmove and unbind when it hits certain criteria

my goal is detect when an element has reached a certain margin-left, and than unbind or stop the panmove from continuing if it hits that threshold.
I have a "panmove" bound to an element using hammer.js, and jquery hammer plugin.
I noticed that in the panmove, console.log(e) will fire hundreds of times as you move the elements, which is expected. If you however put an if statement in the panmove function, it only goes off of the initial state of the first panmove and not the current one.
.bind("panmove", function (e) {
var count = 0;
console.log(e);
console.log(count++);
var _this = $(e.target);
var _thisDataLeft = _this.attr("data-left");
var _thisDataMaxLeft = _this.attr("data-maxleft"); // this is derived from the width of the Delete box, which can be any width.
if (Math.abs(_thisDataLeft) < Number(_thisDataMaxLeft)) {
_this.css({ left: Number(_thisDataLeft) + e.gesture.deltaX }); // controls movement of top layer
console.log(count++);
}
I noticed that the console.log(count++) always fires 1, instead of iterating up, as if it is only reading it once in the beginning.
How can I run an if statement inside of this Pan, so that it is always the current information, and not just the first iteration?
Ended up moving away from Hammer.js, was not able to get the results I needed. It looks like the more basic jquery.event.move.js was easier to use than hammer.
here is my example in js fiddle
https://jsfiddle.net/williamhowley/o9uvo50y/
$(document).ready(function () {
// http://stephband.info/jquery.event.move/
// http://stephband.info/jquery.event.swipe/
// add swipe functionality to the rows.
// I think you will need to add the swipe left, after it is activated by a HOLD down press.
// idk, how do you always make something swipable.
var wrap = $('ul#main');
$('ul#main > li')
.on('movestart', function (e) {
console.log("move start");
// var $li = $(e.target).closest('.swipable'); // this would be normal live integration
var $li = $(e.target);
if ($li.attr("data-hasplaceholder") !== "true") { // if it does not have a placeholder, add one.
createBackgroundSpacer($li);
$li.attr("data-hasplaceholder", true); // signify that a placeholder has been created for this element already.
}
// If the movestart heads off in a upwards or downwards
// direction, prevent it so that the browser scrolls normally.
if ((e.distX > e.distY && e.distX < -e.distY) ||
(e.distX < e.distY && e.distX > -e.distY)) {
e.preventDefault();
return;
}
// To allow the slide to keep step with the finger,
// temporarily disable transitions.
wrap.addClass('notransition'); // add this to the container wrapper.
})
.on('move', function (e) {
// event definitions
// startX : 184, where from left the mouse curser started.
// deltaX: ?
// distX: how far the mouse has moved, if negative moving left. Still need to account for double movement, currently can only handle one movement.
console.log("move");
console.log(e);
var maxLeft = $('.rightContent').width();
var marginLeftNum = Number($(this).css('margin-left').replace(/[^-\d\.]/g, ''));
if (marginLeftNum <= -maxLeft && e.deltaX < 0) { // Case when user is at outermost left threshold, and trying to move farther left.
$(this).css({ 'margin-left': -maxLeft });
}
else if (marginLeftNum == -maxLeft && e.deltaX > 0) { // When user is at threshold, and trying to move back right.
$(this).css({ 'margin-left': marginLeftNum + e.deltaX });
}
else if (e.target.offsetLeft>=0 && e.deltaX>0) { // If the offset is 0 or more, and the user is scrolling right (which is a positive delta, than limit the element. )
$(this).css({ 'margin-left': 0 });
}
// Must have a Negative offset, and e.deltaX is Negative so it is moving left.
else if (e.deltaX < 0) { // Case when element is at 0, and mouse movement is going left.
$(this).css({ 'margin-left': marginLeftNum + e.deltaX });
}
else { // Moving Right when not on 0
$(this).css({ 'margin-left': marginLeftNum + e.deltaX });
}
})
.on('swipeleft', function (e) {
console.log("swipeleft");
})
.on('activate', function (e) {
// not seeing this activate go off, i think this is custom function we can add on if swipe left hits a threshold or something.
console.log("activate");
})
.on('moveend', function (e) {
console.log("move end");
wrap.removeClass('notransition');
});
var createBackgroundSpacer = function ($shoppingListRow) {
var border = 2;
$shoppingListRow.css({ 'width': $shoppingListRow.width() + border, 'height': $shoppingListRow.height() + border }); // gives itself set width and height
$shoppingListRow.addClass('swipable');
// placeholder HTML
var leftPlaceholder = $('<div class="leftPlaceholder"></div>').css({ 'height': $shoppingListRow.height()});
var rightPlaceholder = $('<div class="rightPlaceholder"></div>')
var rightContent = $('<div class="rightContent">Delete</div>').css({ 'height': $shoppingListRow.height()});
rightPlaceholder.append(rightContent);
var placeHolder = $('<div class="swipePlaceholder clearfix"></div>'); // goes around the two floats.
placeHolder.css({ 'width': $shoppingListRow.width(), 'height': $shoppingListRow.height() });
placeHolder.append(leftPlaceholder, rightPlaceholder);
$shoppingListRow.before(placeHolder); // adds placeholder before the row.
$shoppingListRow.css({ 'marginTop': -($shoppingListRow.height() + border) });
};
});

Jquery Mobile + Phonegap improve listview performance

In my JQM 1.4 + Phonegap 3.6 app, I am using a listview as in the following code.
HTML:
<div id="boardselection">
<ul id="modelsListview" data-role="listview" data-icon="false">
</ul>
</div>
JS:
function resetModelsListView(prodata, firsttime, funfeatureOn, specificBrand, specificPro) {
console.log("on passe dans resetModelsListView");
// funfeatureOn = 0;
//debug timer
var time = [];
var dummy;
var i;
var listviewdeferred = $.Deferred();
var optionspro = '';
var optionsbrand = '';
var optionsmodel = '';
var countpros = 0;
var countbrands = 0;
var countmodels = 0;
var chosenmodelListViewHandle = $('#modelsListview');
var chosenbrandSelect = $('#chosenbrand');
optionsmodel += '';
var alreadyusedbrands = [];
prodata.sort(SortByName);
// get previously selected model to reselect it later
//var previouslySelectedModelId =parseInt(chosenmodelSelect.find('li:selected').val());
if (!funfeatureOn) {
prodata.sort(SortByModel);
} else {
prodata.sort(SortByFUN);
}
//populate model list
//~ if (firsttime){
//~ var perfIsChecked = true;
//~ var smallwaveIsChecked = true;
//~ var stepupIsChecked = true;
//~ }else {
var perfIsChecked = $('#checkboxperf').is(":checked");
var smallwaveIsChecked = $('#checkboxsmallwave').is(":checked");
var stepupIsChecked = $('#checkboxstepup').is(":checked");
//~ }
console.log("perfIsChecked, smallwaveIsChecked, stepupIsChecked =");
console.log(perfIsChecked);
console.log(smallwaveIsChecked);
console.log(stepupIsChecked);
//if none checked then no filter
if (!perfIsChecked && !smallwaveIsChecked && !stepupIsChecked) {
perfIsChecked = true;
smallwaveIsChecked = true;
stepupIsChecked = true;
}
for (i = 1; i < prodata.length; ++i) {
if (specificBrand && prodata[i]['brand'] != specificBrand) {
} else if (specificPro && prodata[i]['name'] != specificPro) {
} else {
if (prodata[i]['fun'] == 0 && perfIsChecked) {
optionsmodel += '<li><a class="optionfuninit" href="#" data-proid="' + prodata[i]['id'] + '"><div class="listviewtexts"><span class="listviewtextsmodel">' + prodata[i]['model'] + '</span> - <span class="listviewtextspro">as surfed by ' + prodata[i]['name'] + '</span></div></a></li>';
} else if (prodata[i]['fun'] == 1 && smallwaveIsChecked) {
optionsmodel += '<li><a class="optionfuninit" href="#" data-proid="' + prodata[i]['id'] + '"><div class="listviewtexts"><span class="listviewtextsmodel">' + prodata[i]['model'] + '</span> - <span class="listviewtextspro">as surfed by ' + prodata[i]['name'] + '</span></div></a></li>';
} else if (prodata[i]['fun'] == 2 && stepupIsChecked) {
optionsmodel += '<li><a class="optionstepupinit" href="#" data-proid="' + prodata[i]['id'] + '"><div class="listviewtexts"><span class="listviewtextsmodel">' + prodata[i]['model'] + '</span> - <span class="listviewtextspro">as surfed by ' + prodata[i]['name'] + '</span></div></a></li>';
} else if (prodata[i]['fun'] == 3 && smallwaveIsChecked) {
optionsmodel += '<li><a class="optionkidsinit" href="#" data-proid="' + prodata[i]['id'] + '"><div class="listviewtexts"><span class="listviewtextsmodel">' + prodata[i]['model'] + '</span> - <span class="listviewtextspro">as surfed by ' + prodata[i]['name'] + '</span></div></a></li>';
}
if (prodata[i]['model'] !== prodata[i - 1]['model']) { //eliminate name duplicates if prodata sorted by model
countmodels = countmodels + 1;
}
}
}
chosenmodelListViewHandle.html(optionsmodel);
if (chosenmodelListViewHandle.listview("option", "disabled")) {
chosenmodelListViewHandle.listview("option", "disabled", false);
}
//~ if (resetModelsOnly) {
//~ if ( !isNaN(previouslySelectedModelId) ) {
//~ chosenmodelListViewHandle.find('li[href="' + previouslySelectedModelId + '"]').attr("selected", "selected").siblings('li').removeAttr('selected');
//~ }
//~ }
//~ highlightFunModels(funfeatureOn, 1);
//~ highlightStepupModels(funfeatureOn, 0);
chosenmodelListViewHandle.listview("refresh", true);
$("#chosenmodel-button").addClass("ui-icon-carat-d ui-btn-icon-right");
if (!funfeatureOn) {
} else {
$('ul#chosenmodel-menu').find("a.ui-btn:contains(SMALL-WAVE)").addClass("optionfun");
$('ul#chosenmodel-menu').find("a.ui-btn:contains(STEP-UP)").addClass("optionstepup");
}
prodata.sort(SortById); //we need this otherwise prodata is not usable by the $('#chosenpro').trigger
$("#chosenmodel-button span").attr({ 'data-i18n': 'select.3' });
$("#boardselection").i18n();
listviewdeferred.resolve();
return listviewdeferred;
}
This dynamically displays a long list of images and text, depending on wether filters (checkboxes) are checked or not, and this list is quite long to generate and particularly difficult to scroll in iOS.... The performance is bad.
Can you help me figure out a way to improve the performance.
The accepted answer is not correct. Your code is slow, but not because of jQuery Mobile. Take a look at the loop in your jsFiddle: jsfiddle.net/L3gr46s8/4
for (i = 0; i <= 50; i++) {
$('ul[data-role="listview"]').append('<li>' + 'list item ' + i + '</li>');
$('ul[data-role="listview"]').listview('refresh');
}
There are a couple very significant problems in those four lines of code.
First, your code executes two DOM traversals to locate the ul on the page. You could execute $('ul[data-role="listview"]') before the loop and store the result in a variable:
var listView = $('ul[data-role="listview"]');
Second, your code inserts the list item directly to the DOM and you instruct jQuery Mobile to immediately apply markup enhancement with .listview('refresh');. This is extraordinarily expensive! Especially on under-powered mobile devices. Depending upon the browser and page layout, those two lines could trigger a full page re-draw with every iteration of the loop.
You should render the content as a DocumentFragment (in memory), insert it all to the DOM in one action and tell JQM to enhance the markup once at the end. Even simply moving $('ul[data-role="listview"]').listview('refresh'); out of the loop would be a dramatic improvement.
Here is some additional reading on the importance of rendering your content first in memory before inserting it into the DOM:
How expensive is it to dynamically insert DIVs using JavaScript?
John Resig - DOM DocumentFragments
To put it short, jQuery Mobile is simply slow.
I have a dynamic listview in my app and also had performance issues when using jQuery Mobile. I came to a conclusion that the issue was in rendering and was caused by jQuery Mobile. I implemented my own styling and the rendering time came down from 170ms to 25ms.
Here's some backup to my point (3 articles): http://apachecordova.blogspot.fi/search/label/jQuery%20Mobile
EDIT:
As an answer to your question in the comments, I don't think it would help if I posted my code here. The whole point is that you only write the code YOU need. My listview probably is totally different than yours.
To prove my point (again), I made two listviews. The first one is a basic jQM listview. The other one is styled with custom CSS and it's pretty close to what I use in my app. There's a button in both which renders the listview. What is happening under the hood is very different:
jQM: As you can see, there's a lot of stuff (that you may not need) going on
Custom CSS: attached an event listener to all the elements to make the comparison more fair
These profiles have been recorded with Chrome Developer Tools and the difference is obvious: 173ms vs 12ms. This custom CSS took me about 5min to write:
#custom-listview {
list-style-type: none;
padding: 0px;
margin: 0px;
}
#custom-listview li {
display: block;
position: relative;
overflow: visible;
}
#custom-listview a {
display: block;
position: relative;
text-overflow: ellipsis;
text-decoration: none;
color: white;
padding: .7em 1em;
font-size: 16px;
background-color: #333;
border: solid 1px #1f1f1f;
overflow: hidden;
white-space: nowrap;
font-family: sans-serif;
}
I had to add some code here, because SO won't let me link to Fiddle without:
jQM
Custom CSS
I'm not saying jQuery Mobile is all bad. It's good for many things. But if you have complex structure and/or a lot of data, the performance may become an issue especially in PhoneGap apps. That's the conclusion I have come to with my little experience.

Raphael JS combined elements with separate control: Removing Event Part 2

This is a continuation of an earlier thread, that was answered by Neil- thanks Neil! I probably should have included this in the first question, but I wanted to simplify things...
Another feature that I need is to have a "dialogue box" that holds a title and some text that animates on and off next to the circle when it comes on. I achieved this in my earlier version prior to the help from Neil. I have spent some time trying to integrate it into the new and improved code and get some unexpected results. For example, if I rollover the first circle on the right, it works as it should, however, if I try to rollover the middle and right circles, they don't work. Oddly, if I refresh and start on the right circle, each will work when moving right to left, until I reach the left one, and then the middle and right don't work- but the left one continues to work. Additionally, if I click on the left circle, it works as it should, but then the others don't work. And conversely, if I click on the right one first, and then move to the middle, the middle works on click, but then the right one does not.
The behavior that I am looking for is that each circle, animate up with the rectangle next to the circle fading in with dynamic text on mouseover and animate down, with the rectangle with text fading out on mouseout. The rectangle with text should fade out on click and not fade up again if the user mousesover the clicked circle (need to remove the mouseover event as well I guess). One additional thing that needs to happen is that the rectangle needs to appear in a different place on the circle, depending where on the map it is- so that it doesn't fall off the map. I did this successfully in the earlier version, but have left that out on the previous post for better clarity. I will include it here so you get the gist of what I'm doing.
My guess is that I need to create a set() of the rectangle/text component and place it in an another set() along with the circle?
Any help on this is truly appreciated! Thanks
// JavaScript Document
var arr = [
[50, 50, "this", "Is text that is to be the abstract"],
[100, 50, "that", "Is text that I hope is here"],
[150, 50, "another thing", "Even more text"]
];
var currRect;
var currTitleTxt;
var currTeaseTxt;
var prevItem;
doMe();
function doMe() {
var paper = new Raphael(document.getElementById('canvas_container'), 696, 348);
for (var i = 0; i < arr.length; i++) {
paper.circle(arr[i][0], arr[i][1], 6).attr({
fill: '#fff',
'fill-opacity': 0.5
}).data("i", [arr[i][0], arr[i][1], arr[i][2], arr[i][3]]).click(function () {
this.unmouseout();
}).click(function () {
if (this.data('selected') != 'true') {
if (prevItem != null) {
prevItem.data('selected', '');
handleOutState(prevItem);
}
prevItem = this.data('selected', 'true');
currRect.animate({"fill-opacity":0, "stroke-opacity":0}, 150 );
currTitleTxt.animate({"fill-opacity":0}, 150 );
currTeaseTxt.animate({"fill-opacity":0}, 150 );
}
}).mouseover(function () {
handleOverState(this);
if(this.data("i")[0] <= 350){ //this is where I test for the position on the map
paper.setStart(); //create rectangle and text set
currRect =paper.rect(17, -20, 265,90).attr({fill:"#999","fill-opacity":0.5});
currTitleTxt = paper.text(25, -8, this.data("i")[2]).attr({"text-anchor":"start",fill:'#ffffff',"font-size": 14, "font-weight":"bold","fill-opacity":0});
currTeaseTxt = paper.text(25, 30).attr({"text-anchor":"start",fill:'#eeeeee',"font-size": 11, "font-weight":"bold","fill-opacity":0});
var maxWidth = 250;
var content = this.data("i")[3];
var words = content.split(" ");
var tempText = ""; //since Raphael doesn't have native word wrap, I break the line manually
for (var i=0; i<words.length; i++) {
currTeaseTxt.attr("text", tempText + " " + words[i]);
if (currTeaseTxt.getBBox().width > maxWidth) {
tempText += "\n" + words[i];
} else {
tempText += " " + words[i];
}
}
currTeaseTxt.attr("text", tempText.substring(1));
var st = paper.setFinish();
st.translate(this.data("i")[0]+10, this.data("i")[1]+0).animate({"fill-opacity":1}, 150 );
}else if(this.data("i")[0] >= 351){ //this is where I test for the position on the map
paper.setStart();
currRect = paper.rect(-280, -20, 250,50).attr({fill:"#999","fill-opacity":0.5});
currTitleTxt = paper.text(-270, -10, this.data("i")[2]).attr({"text-anchor":"start",fill:'#ffffff',"font-size": 14, "font-weight":"bold","fill-opacity":0});
currTeaseTxt =paper.text(-270, 5, this.data("i")[3]).attr({"text-anchor":"start",fill:'#cccccc',"font-size": 12, "font-weight":"bold","fill-opacity":0});
var st = paper.setFinish();
st.translate(this.data("i")[0]+10, this.data("i")[1]+0).animate({"fill-opacity":1}, 150 );
}
}).mouseout(function () {
currRect.animate({"fill-opacity":0, "stroke-opacity":0}, 150 );
currTitleTxt.animate({"fill-opacity":0}, 150 );
currTeaseTxt.animate({"fill-opacity":0}, 150 );
if (this.data('selected') != 'true') handleOutState(this);
});
}
function handleOverState(el) {
el.animate({
r: 8
}, 250).animate({
"fill-opacity": 1
}, 150);
}
function handleOutState(el) {
el.animate({
r: 6
}, 250).animate({
"fill-opacity": 0.5
}, 150);
}
}

Resources