Animate text in div element together with Raphael SVG element - javascript

I would like to get a button-like object consisting of a SVG rectangle as the background and HTML text in the foreground. The Raphael SVG library offers nice possibilities to create, modify and animate SVG elements.
I found a solution to draw HTML text in a DIV element along/over the Raphael objects. How can I use the animating methods from Raphael to make the text move with the background?
I am only interested in basic 2D movement, no rotation or deformation ...
I know I could use SVG text element which is supported by Raphael, but this does not allow me text-wrapping, text-styling (css). I was tried unsuccessfully to find a method to get the coordinates of the animated object during animation.
Here is some sample code in coffeescript inspired by the above example mad eby Kelley Reynolds. My problem is how to synchronize thy movement of the background with the overlaying div :
nodeBox = (paper, params, attrs) ->
params = params or {}
attrs = attrs or {}
#paper = paper
#x = params.x or 0
#y = params.y or 0
#width = params.width or #paper.width
#height = params.height or #paper.height
#xMargin = params.xMargin or 5
#yMargin = params.yMargin or 5
#rounding = params.rounding or 0
#backgBox = this.paper.rect(#x-#xMargin, #y-#yMargin, #width+2*#xMargin, #height+2*#yMargin, this.rounding).attr(attrs)
containerId = #backgBox.node.parentNode.parentNode.id
containerId = containerId or #backgBox.node.parentNode.parentNode.parentNode.id
#container = jQuery('#' + containerId)
#div = jQuery('<div style="position: absolute; overflow: auto; width: 0; height: 0;"></div>').insertAfter(#container)
jQuery(document).bind('ready', this, (event) ->
event.data.update()
)
jQuery(window).bind('resize', this, (event) ->
event.data.update()
)
return this
# update function
nodeBox::update = () ->
offset = #container.offset()
#div.css(
'top': (#y + (#rounding) + (offset.top)) + 'px',
'left': (#x + (#rounding) + (offset.left)) + 'px',
'height': (#height - (#rounding*2) + 'px'),
'width': (#width - (#rounding*2) + 'px')
)
# animate function
nodeBox::animate = (attrs, duration, type) ->
#backgBox.animate(attrs, duration, type)
###
Animation of the div ???
###
$(document).ready ->
paper = new Raphael document.getElementById('canvas_container'), '100%', '100%'
node1 = new nodeBox(paper, x:200, y:200, width:200, height:60, rounding: 10, xMargin: 8, yMargin: 4, 'showBorder': true).attr(fill: 'lightblue', stroke: '#3b4449', 'stroke-width': 3)
node1.div.html('This is some crazy content that goes inside of that box that will wrap around.')
node1.div.css('font-family': 'Helvetica','font-size': 16, 'text-align': 'justify')
# animate :
node1.animate({x: moveX}, 1000, 'bounce')

So I figured this out by myself. First possibility is to use the eve library, which already comes with Raphael 2.x. You can update the coordinates of the div on every frame of the animation corresponding to the coordinates of the background-box using eve.on() :
nodeBox::animate = (attrs, duration, type) ->
offset = #canvasContainer.offset()
strokeWidth = #backgBox.attrs['stroke-width']
textBox = #div
rounding = #rounding
xMargin = #xMargin
yMargin = #yMargin
### we animate the underlying box
#backgBox.animate(attrs, duration, type, eve.unbind('raphael.anim.frame.' + #backgBox.id, onAnimate))
### the coordinates of the div are updated on every frame
eve.on('raphael.anim.frame.' + #backgBox.id, onAnimate = (animationObject) ->
textBox.css(
'top': (#attrs.y + (rounding) + (yMargin) + (offset.top) + strokeWidth) + 'px',
'left': (#attrs.x + (rounding) + (xMargin) + (offset.left) + strokeWidth) + 'px', # scrollbar width
'height': (#attrs.height - (rounding*2) - (yMargin*2) - strokeWidth + 'px'),
'width': (#attrs.width - (rounding*2) - (xMargin*2) - strokeWidth + 'px')
)
)
The downside of this solution is, that it does not feel very smooth, one can already see the text "following" the box. Therefore I am no trying the GreenSock Animation Platform which comes with an plugin for Raphael and should offer synchronous animation of the vector graphics and the overlying DOM. Take a look here

Related

Create div overlay for image based on xml attributes

I want to create a div overlay, set at 5% opacity, which creates a diagonal bounding box around each of the lines of text in this image. I have the coordinates of each of the four points I need to draw the bounding box, which are encoded as attributes in an XML element which contains the text of individual line as its data. It seems to me that since I have the x,y coordinates for the four corners, I should be able to create the overlay -- but I cannot figure out what the proper syntax would be to mark out the div. Someone suggested to me using the transform functions for CSS (which was the right call as I originally framed this question) but that sounds like I'd be basically writing eight separate pieces of css, one for each line -- which could get messy since there are potentially 118 pictures like this that I would be writing custom pieces of CSS for.
Am I wrong in thinking this can be done programmatically, and if not can someone point me at some methods for doing so?
Yes, it is possible this way, with simple html markup:
<div class="image_box" data-cords="20,50,210,50,50,250,240,250">I am the text inside the div, I can be in several lines. It is important for text stay vertical and it is important the text to follow boundary box.</div>
Than do some script magic:
$.fn.skewText = function () {
return this.each(function (i) {
var thisText = $(this);
var coords = thisText.data('cords').split(',');
/* Calculate degree */
var deg = Math.tan((coords[5] - coords[1]) / (coords[4] - coords[0])) * 57.2957795;
/* Skew pixels for calculations */
var skewVal = coords[4] - coords[0];
/* Calculate position */
var cssWidth = (coords[2] - coords[0] - skewVal) + 'px';
var cssHeight = (coords[5] - coords[1]) + 'px';
var cssTop = coords[1] + 'px';
var cssLeft = (coords[0] * 1 + skewVal) + 'px'; /* Add a half of skew */
/* Wrapp and apply styles */
thisText.wrapInner('<div class="skew_padding" />').wrapInner('<div class="skew_text" />');
var skewText = thisText.find('.skew_text');
skewText.css({
'transform': 'skew(' + deg + 'deg)',
'top': cssTop,
'left': cssLeft,
'width': cssWidth,
'height': cssHeight,
'background': 'none rgba(0,0,0,.5)'
});
/* Now skew back each letter inside */
var skewPadding = skewText.find('.skew_padding');
var letters = skewPadding.text().split(' ');
var newText = '<span>' + letters.join('</span> <span>') + '</span>';
skewPadding.html(newText);
skewPadding.find('span').css({
'display': 'inline-block',
'transform': 'skew(' + (-deg) + 'deg)'
});
});
};
$('[data-cords]').skewText();
That is possible with this css:
.image_box {
position: relative;
width: 300px;
height: 300px;
background: yellow;
}
.skew_text {
position: absolute;
}
.skew_padding {
padding: 10px;
}

How to center a group of SVG shapes with Snap.SVG & Javascript

I'm trying to make a circle of rectangles, to be centered (vertically and horizontally) in an SVG
here's my code so far:
http://jsfiddle.net/JamThom/7G2pC/
var a = Snap(250,250);
for (var i=0;i<90;i++) {
var sqz = a.rect(0, 0, 14, 3)
.attr({
fill: '#666',
transform: "r"+i*4+",75,0"
})
.data("i",i);
}
In order to do this I believe I need to first select and group all of the rectangles, but as I created them in a for loop they don't have individual names and I don't know how to target them
any help appreciated
and apologies for the ameteur-ness of my code - this is my first experience with snap.svg
You can move it to the center using translate:
transform: "t50,125 r"+i*4+",75,0"
JSFiddle
Your SVG is 250x250 and the radius is 75, so the coordinates for t are 250 / 2 - 75 = 50 and 250 / 2 = 125. In the fiddle I used variables.
Just as a slight variation to helderdarocha for an answer, you can as you mention add them to a group and translate, like this fiddle here
var skillometer = Snap(size, size);
var g = skillometer.g();
g.attr({ transform: 't' + x + ',' + y });
for (var i = 0; i < 90; i++) {
var sqz = skillometer.rect(0, 0, 14, 3)
.attr({
fill: '#666',
transform: "r" + i * 4 + "," + radius + ",0"
}).data("i", i);
g.append( sqz );
}
// you can now animate or move the whole group as one.
g.animate({ transform: 't'+x+','+y+'s1.65' }, 5000 );
There's not a lot of difference, unless you want to move or animate the whole lot, in which case this group method may be preferable, as you can move the group in the future with a transform on just the group element (have included that just to highlight).

Modifying the file jquery.fancybox-1.3.4.js to rewrite _draw function, Jquery

I am doing a gallery based on fancybox - actually I have modified to field to achieve what I need, and I've done it, the last issue I found is in this part of the code:
} else {
fx.prop = 0;
$(fx).animate({prop: 1}, {
duration : currentOpts.changeSpeed,
step : _draw,
complete : finish_resizing
});
The Draw function is here so you can see:
_draw = function(pos) {
var dim = {
width : parseInt(start_pos.width + (final_pos.width - start_pos.width) * pos, 10),
height : parseInt(start_pos.height + (final_pos.height - start_pos.height) * pos, 10),
top : parseInt(final_pos.top, 10),
left : parseInt(final_pos.width, 10)
};
if (typeof final_pos.opacity !== 'undefined') {
dim.opacity = pos < 0.8 ? 0.8 : pos;
}
wrap.css(dim);
content.css({
'width' : limit.width - currentOpts.padding * 2,
'height' : limit.height - (titleHeight * pos) - currentOpts.padding * 2
});
},
My question is how to modify the _draw function, or how to set a animate function, for NEXT and for PREV. Also disable opacity.
I need something like a "scrollable" function.. on the main image of Fancybox (kind of like this http://jquerytools.org/demos/scrollable/)
So I just can't manage to get this done..
Is there other way from the fancybox options? because I can't find them.
Anyway, this was the original file -
http://demo-store.prestashop.com/js/jquery/jquery.fancybox-1.3.4.js
These are the changes I made on line 663:
_draw = function(pos) {
var dim = {
width : parseInt(start_pos.width + (final_pos.width - start_pos.width) * pos, 10),
height : parseInt(start_pos.height + (final_pos.height - start_pos.height) * pos, 10),
top : parseInt(final_pos.top, 10),
left : parseInt(final_pos.width, 10)
};
if (typeof final_pos.opacity !== 'undefined') {
dim.opacity = pos < 0.8 ? 0.8 : pos;
}
And also this (but I don't think this affects it):
$('body').append(
tmp = $('<div id="fancybox-tmp"></div>'),
loading = $('<div id="fancybox-loading"><div></div></div>'),
overlay = $('<div id="fancybox-overlay"></div>'),
limit = $('<div id="limit"></div>')
);
wraplimit = $('<div id="wraplimit"></div>').append( limit ).appendTo('body');
thebox = $('<div id="thebox"></div>').append('<div id="comrigtt"></div>').appendTo( overlay )
wrap = $('<div id="fancybox-wrap"></div>').append().appendTo( limit );
outer = $('<div id="fancybox-outer"></div>')
.append('<div class="fancybox-bg" id="fancybox-bg-n"></div><div class="fancybox-bg" id="fancybox-bg-ne"></div><div class="fancybox-bg" id="fancybox-bg-e"></div><div class="fancybox-bg" id="fancybox-bg-se"></div><div class="fancybox-bg" id="fancybox-bg-s"></div><div class="fancybox-bg" id="fancybox-bg-sw"></div><div class="fancybox-bg" id="fancybox-bg-w"></div><div class="fancybox-bg" id="fancybox-bg-nw"></div>')
.appendTo( wrap );
It sounds like you are wanting to add custom animations for next and prev that differ from the main animation? Have you checked the docs for http://fancybox.net/api and utilize the next and prev commands...
If what you want is the effet you posted in the fiddle then I would suggest the following nasty hack, (or drop fancybox and grab one that supports that effect out of the box).
1) enable fancybox on your trigger
2) use jQuery or other to create your thumbnails in a div and then append that div to the bottom of the created fancybox elements (down at the bottom of body)
3) wire up your created thumbnails to use the existing fancybox api for next/prev/ specific etc.
4) avoid having to touch the js itself.

Trying to retrieve Rendered Text Width in Raphael

So I am looking to retrieve the size of a text string in Raphael and I can't seem to do it. Although the documentation says that
.attr('width');
is an option...and I can't set the width either.
Here is a FIDDLE
This is what I have been trying...plus other crude ways (even using Jquery)
var page = Raphael("drawing_board");
// start, move, and up are the drag functions
start = function () {
// storing original coordinates
this.ox = this.attr("x");
this.oy = this.attr("y");
this.attr({opacity: .5});
console.log( this.attr('width') ); //THIS IS WHERE I AM TRYING TO GET THE WIDTH
},
move = function (dx, dy) {
// move will be called with dx and dy
nowX = Math.min(600, this.ox + dx);
nowY = Math.min(400, this.oy + dy);
nowX = Math.max(0, nowX);
nowY = Math.max(0, nowY);
this.attr({x: nowX, y: nowY });
},
up = function () {
// restoring state
this.attr({opacity: 1});
};
page.text(200, 50, "TEXT 1").attr({ cursor: "move", 'font-size': 16 , fill: '#3D6AA2'}).drag(move, start, up);
page.text(200, 200, "TEXT 2").attr({ cursor: "move", 'font-size': 16 , fill: '#3D6AA2'}).drag(move, start, up);
Maybe I need to use something other than
this.attr('width' : 30); //To Set
this.attr('width'); //To Get
You can use:
this.node.getBBox().width
this.node -> gets the SVG element associated with the Raphael element
getBBox() -> bounding box
You can also use Raphael's getBBox method directly:
this.getBBox().width
Your approach doesn't work because the svg text element doesn't have a width attribute.
this.getBBox() is the RaphaelJS method to get the computed bbox thus returning an object with x y x1 x2 width and height properties. It's as cross browser as Raphaeljs is.
SVG Text elements doesn't have the width attribute. Check it on the w3c SVG specification: http://www.w3.org/TR/SVG/text.html#TextElement There's no width on the attributes list, so you can't set it.
But we could get the width of the DOM element with jQuery. It can be done first retrieving the DOM element reference and passing it to the jQuery constructor, then using the width() function. Here's the updated fiddle: http://jsfiddle.net/Rmegm/8/

Can jQuery copy an element's bounds (position, size, margins, etc.) to another element?

I have an element of an arbitrary type. I'd like to create another element, of either the same or a different type that has the same position and size as the first. The element may or may not be positioned.
For example, I might start with a <select> with a certain size, possibly dependent on its contents, i.e. width/height auto. I want to create a new <div> that appears at the same position and has the same size.
I've tried copying the element's float, clear, position, width, height, margins and padding, but this is a little cumbersome. Also, while it works in Firefox, I'm running into some strange issues when testing on Webkit. Before I spend much more time figuring it out, I'd like to know whether there's some jQuery or jQuery UI functionality that already takes care of what I want to do.
I realize that this question is similar to an existing one, but mine has the important distinction of needing to work with elements of differing types, which precludes clone as a solution.
This is NOT efficient, tested, or complete. And it is probably similar to what you are already doing. But I thought I'd post it anyways:
var positioningProps = ["float","position","width","height","left","top","marginLeft","marginTop","paddingLeft","paddingTop"];
var select = $("#mySelect");
var div = $("<div>").hide().before(select);
// don't do this kind of loop in production code
// http://www.vervestudios.co/jsbench/
for(var i in positioningProps){
div.css(positioningProps[i], select.css(positioningProps[i])||"");
}
select.hide();
How about just copying the element's offset and absolutely positioning it on the page?
Say you had an input element somewhere on the page with dimensions 100x25px.
<input type="text" id="firstname" style="width: 100px; height: 20px" />
And you wanted to place a div right on top of it (and hide the input).
// Store the input in a variable
var $firstname = $("#firstname");
// Create a new div and assign the width/height/top/left properties of the input
var $newdiv = $("<div />").css({
'width': $firstname.width(),
'height': $firstname.height(),
'position': 'absolute',
'top': $firstname.offset().top,
'left': $firstname.offset().left
});
// Add the div to the body
$(body).append($newdiv);
You can find out an elements bounds using this plugin to jQuery. The it would be just a simple matter of setting whatever properties you are interested in to the other object.
http://code.google.com/p/jquery-ui/source/browse/branches/labs/powella/coverslide/res/js/jquery/jquery.bounds.js?r=2698
/*
* jQuery.bounds
* author: Andrew Powell
*/
(function($){
$.fn['bounds'] = function()
{
var t = this, e = t[0];
if (!e) return;
var offset = t.offset(), pos = { width:e.offsetWidth, height:e.offsetHeight, left: 0, top: 0, right: 0, bottom: 0, x: 0, y: 0 };
pos.left = offset.left;
pos.top = offset.top;
//right and bottom
pos.right = (pos.left + pos.width);
pos.bottom = (pos.top + pos.height);
pos.x = pos.left;
pos.y = pos.top;
pos.inner = {width: t.width(), height: t.height()};
$.extend(pos, {toString: function(){ var t = this; return 'x: ' + t.x + ' y: ' + t.y + ' width: ' + t.width + ' height: ' + t.height + ' right: ' + t.right + ' bottom: ' + t.bottom; }});
return pos;
};
})(jQuery);

Categories