SVG: Scale one element of group from specified anchor point - javascript

OK, so I have a group of four elements rotating 90 degrees as I want them to, around an origin point in the middle of the four elements.
I would like to scale the top left block before and after spinning as well, outward from said origin point, but I'm having much difficulty doing so.
Here is a fiddle for my sample (read: overly simplified) progress so far:
http://jsfiddle.net/Vac2Q/2843/
The fiddle's javascript:
/* create an svg drawing */
var draw = SVG('drawing')
/* draw rectangle */
var dial = draw.circle(60)
.move(125,125)
.fill('#0099ff')
var rect_yellow = draw.rect(50,50)
.move(100,100)
.fill('gold')
var rect_blue = draw.rect(50,50)
.move(160,100)
.fill('navy')
var rect_black = draw.rect(50,50)
.move(160,160)
.fill('black')
var rect_green = draw.rect(50,50)
.move(100,160)
.fill('green')
var blades = draw.group()
.add(rect_yellow)
.add(rect_blue)
.add(rect_black)
.add(rect_green)
.back()
var angle = 0
var rotation = 90
var spin = document.getElementById('spin')
var spun = 0
/* make rectangle jump and change color on mouse over */
spin.addEventListener('click', function() {
/* calculate new ending orientation for blades */
angle += rotation
var new_rotate = angle
/* rotate the blade group */
blades.animate(1000, '>')
.rotate(new_rotate, 155, 155)
++spun
})
And here is a slightly more glamorous example of what I'm trying to do re: scaling:
The first issue is being able to determine which blade is in the top left position after a given rotation. The second issue is scaling itself; I've gotten the blade to scale, but then it goes crazy and moves off the screen at the same time. I'm not sure how to get it to scale properly from the specified origin point (the middle of the center circle).

You can use the .after() function to chain animations.
I'm not sure if I am using svg.js correctly, but here's what I did:
var rects = [rect_yellow, rect_green, rect_black, rect_blue];
// define the animations
var enlarge_blade = function() {
rects[spun % 4].animate(250, '<')
.scale(1.25, 1.25)
.translate(-38,-38);
};
function spin_anim() {
rects[spun % 4].animate(250, '>')
.scale(1, 1)
.translate(0,0)
.after(rotate_blades);
};
var rotate_blades = function() {
blades.animate(1000, '>')
.rotate(angle, 155, 155)
.after(function() {
++spun;
enlarge_blade();
title.text('spun ' + spun + ' times');
});
};
// Pre-enlarge the first (yellow) rect
enlarge_blade();
/* make rectangle jump and change color on mouse over */
spin.addEventListener('click', function() {
angle += rotation
spin_anim();
})
Demo here

I see you're using svg.js. I don't know how it treats transforms internally. But in any case. To scale an element, you typically use its center point as a reference. Therefore you should find the center point of each rect and scale it using that point. (I assume svg.js performs the required translation internally).

Related

Animate segments and position in Paper.js

I need to develop 2 animations on a polygon:
automatic rotation of each segments
the polygon follows the mouse movements
I'm trying to do that with this code:
// onMouseMove
tool.onMouseMove = function (event) {
mousePoint = event.point;
};
// onFrame
view.onFrame = function (event) {
// Animation 1: Automatic circular rotation of each segment
for (var i = 0; i < polygon.segments.length; i++) {
var offsetRotation = new Point(Math.cos(event.time + i * 2), Math.sin(event.time + i * 2)).multiply(10);
polygon.segments[i].point = polygonCached.segments[i].point.add(offsetRotation);
}
// Animation 2: the polygon moves following the mouse movements with transition
var delta = mousePoint.subtract(polygon.position).divide(15);
polygon.position = polygon.position.add(delta);
};
Here is the whole code: https://codepen.io/anon/pen/YMgEEe?editors=0010
With the above code the automatic rotation of each segments works and the polygon doesn't follow the mouse at all and also without the transition.
Instead, commenting the automatic rotation animation it correctly follows the mouse with transition.
To check if transition works I move the mouse cursor outside of browser window and then go back from another point. Now you'll see no transition when the polygon moves.
Where I'm wrong?
Just move the polygonCached as well.
polygonCached.position = polygonCached.position.add(delta);
https://codepen.io/arthursw/pen/LvKPXo
The cached version of the polygon was not moving, so each time you rotated your points, their position was reset.

SVG rotation with support for all Browsers

I have made a post on Friday already about how starting off to use an SVG animation to show the state of IO hardware.
I've read a bit now because im using CSS like so
svg {
transform: rotate(45deg);
transform-origin: 50% 50%;
}
where sadly the support for IE is by giving the attribute directly to the svg:
<rect x='65' y='65' width='150' height='80'
transform='rotate(45 140 105) rotate(-45)' />
Now I've used a rect as an example but I'm actually using a shape because I need to animate a needle for a Voltmeter.
I could use stroke maybe but it would look better with a real needle like thingy.
Now the problem I have with this is that after designing, there must be a function made in Javascript to get to the different states.
So I wanna rotate the needle for example 2 degrees for every value I get from the IO.
21 states are needed and it would be a hell of work to calculate all the positions for transform rotate when the attribute is set directly onto it.
Is there another way?
Thanks in advance.
Set an id attribute for your needle, so you can target it.
Find the center point to rotate about.
Set the minimum and maximum angle the needle can rotate to.
Set the minimum and maximum value you can receive.
Now, if your SVG content is part of the same document that runs the script, no matter if that is a HTML or SVG file:
var needle = document.getElementById('voltmeterNeedle'); // use your id
If you link the SVG content with a <object> or <iframe> tag from document that runs the script (<img> won't work):
var needle, svg = document.getElementById('embedingTag'); // use your id
svg.addEventListener('load', function () { // wait for load event
needle = svg.contentCocument.getElementById('voltmeterNeedle'); // use your id
});
...continuing for both:
var needleCenterX = 140, needleCenterY = 105;
var minAngle = -45, maxAngle = 45;
var minValue = 0, maxValue = 21;
function setNeedle (value) {
var valueRatio = (value - minValue) / (maxValue - minValue);
var angle = (maxAngle - minAngle) * valueRatio + minAngle;
var rotation = [angle, needleCenterX, needleCenterY].join(' ');
needle.setAttribute('transform', 'rotate(' + rotation + ')');
}

Create a wiggle effect for a text

So what I want to happen is that when viewing the Span the text is normal but as you scroll down it starts moving until it looks like such:
Before the effect:
While the effect occurs:
The header is represented by spans for each letter. In the initial state, the top pixel value for each is 0. But the idea as mentioned is that that changes alongside the scroll value.
I wanted to keep track of the scroll position through JS and jQuery and then change the pixel value as needed. But that's what I have been having trouble with. Also making it smooth has been another issue.
Use the mathematical functions sine and cosine, for characters at even and odd indices respectively, as the graphs of the functions move up and down like waves. This will create a smooth effect:
cos(x) == 1 - sin(x), so in a sense, each character will be the "opposite" of the next one to create that scattered look:
function makeContainerWiggleOnScroll(container, speed = 0.01, distance = 4) {
let wiggle = function() {
// y-axis scroll value
var y = window.pageYOffset || document.body.scrollTop;
// make div pseudo-(position:fixed), because setting the position to fixed makes the letters overlap
container.style.marginTop = y + 'px';
for (var i = 0; i < container.children.length; i++) {
var span = container.children[i];
// margin-top = { amplitude of the sine/cosine function (to make it always positive) } + { the sine/cosine function (to make it move up and down }
// cos(x) = 1 - sin(x)
var trigFunc = i % 2 ? Math.cos : Math.sin;
span.style.marginTop = distance + distance * trigFunc(speed * y)/2 + 'px';
}
};
window.addEventListener('scroll', wiggle);
wiggle(); // init
}
makeContainerWiggleOnScroll(document.querySelector('h2'));
body {
height: 500px;
margin-top: 0;
}
span {
display: inline-block;
vertical-align: top;
}
<h2>
<span>H</span><span>e</span><span>a</span><span>d</span><span>e</span><span>r</span>
</h2>
Important styling note: the spans' display must be set to inline-block, so that margin-top works.
Something like this will be the core of your JS functionality:
window.addEventListener('scroll', function(e) {
var scrl = window.scrollY
// Changing the position of elements that we want to go up
document.querySelectorAll('.up').forEach(function(el){
el.style.top = - scrl/30 +'px';
});
// Changing the position of elements that we want to go down
document.querySelectorAll('.down').forEach(function(el){
el.style.top = scrl/30 +'px';
});
});
We're basically listening in on the scroll event, checking how much has the user scrolled and then act upon it by offsetting our spans (which i've classed as up & down)
JSBin Example
Something you can improve on yourself would be making sure that the letters wont go off the page when the user scrolls a lot.
You can do this with simple math calculation, taking in consideration the window's total height and using the current scrollY as a multiplier.
- As RokoC has pointed out there is room for performance improvements.Implement some debouncing or other kinds of limiters

Adding a scale to canvas image zoom

I'm using a some code from https://github.com/dimxasnewfrozen/Panning-Zooming-Canvas-Demos/blob/master/demo12/main.js (demo at http://dayobject.me/canvas/demo12/) to zoom in on an image using the Canvas element.
However, when zooming the jump between one zoom level and the next is too large, so I need to add a scale parameter.
How would I got about doing this?
In your main.js, you can change your zoomLevel here,
mouseWheel: function (e) {
e.preventDefault() // Please add this, coz the scroll event bubbles up to document.
var zoomLevel = 2;
...
if (delta > 0)
{
// ZOOMING IN
var zoomedX = canvasPos.deltaX - (canvasZoomX - canvasPos.deltaX);
var zoomedY = canvasPos.deltaY - (canvasZoomY - canvasPos.deltaY);
// scale the image up by 2
initialImageWidth = initialImageWidth * zoomLevel;
}
else
{
// ZOOMING OUT
var zoomedX = (canvasPos.deltaX + canvasZoomX);
var zoomedY = (canvasPos.deltaY + canvasZoomY);
// scale the image down by 2
initialImageWidth = initialImageWidth / zoomLevel;
}
}
Disclaimer: this ruins the zoomedX and zoomedY values. You gotta fix them :)
It seams to me as if the algorithm always takes half of the dimension befor the zoom. At the end of you code you see it in main.js the mouseWheel function:
initialImageWidth = initialImageWidth * 2;
the width is divided by 2 so just change the used value.
You said the step used to zoom in and out is too big.
So I suggest that you generate the new value by using the dimensions of the image you want to zoom. For example you take a percentage of the biggest dimension of the current image.
That's how you can implement a zoom function that zooms according to the dimensions of the image

Raphael.js centered text on path

What I have:
Text along a path made out of circle. It uses Raphael.js and a function called textOnPath (found here: Raphael JS Text Along path ):
var pathTest = r.path(getCircletoPath(286, 322, radius)).attr({stroke:"#b9b9b9"});
textOnPath(message, pathTest, fontSize, fontSpacing, kerning, kerning, point, textFill, fontNormal, fontFamily);
JSFiddle: http://jsfiddle.net/zorza/62hDH/1/
What I need:
The text to be centered on top of the circle.
My approach:
Try to calculate where the text should start depending on the arc size and text width. I tried to calculate the text width by creating it's invisible clone with text() function and get it's BBox width.
It doesn't quite work and the results vary depending on the web browser, font used and number of letters and spaces:
var length = r.text(100,400,message)
.attr({"font-size":fontSize,'opacity': 0, 'font-family': fontFamily})
.getBBox()
.width;
var point = (Math.PI*radius - length*fontSpacing)/2;
JSFiddle: http://jsfiddle.net/zorza/k8vBy/3/
Could anyone point me in the right direction?
The easiest way, IMHO, is to create additional helper path that is raised by half of text size. http://jsfiddle.net/p84VQ/
Also, I find it a bit more convenient to define a circle and then get points at specified angle:
var Circle = function(cx, cy, r) {
return function (a) {
return {
x: cx + r*Math.sin(Math.PI*-a/180),
y: cy - r*Math.cos(Math.PI*-a/180)
}
}
};

Categories