I'm trying to implement drawing effect for text from user input so that it looks like it's being written.
Very much like the drawing of "there" on this page: http://maxwellito.github.io/vivus/
Where I'm at: https://jsfiddle.net/w3nmwqgo/1/
SO editor is complaining about no code, so here's the same thing:
var path = document.getElementsByTagName('path')[0];
// this doesn't return true length
var length = path.getTotalLength();
path.style.strokeDasharray = length;
path.style.strokeDashoffset = length;
var intervalId = setInterval(function() {
if (length < 0) clearInterval(intervalId);
path.style.strokeDashoffset = --length;
}, 25);
/* shouldn't need this? */
path {
transform: translate(20px, 100px);
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path fill="none" stroke="black" d="M-7.34 0.29C-7.34 0.22-2.88-6.77 3.24-14.54C11.38-24.84 18.79-31.82 24.70-34.78C24.77-34.85 34.49-39.60 39.89-35.14C41.26-33.98 41.90-32.62 41.98-30.96C42.41-23.69 34.27-17.28 29.88-11.81C26.35-7.42 24.98-4.32 25.63-2.45C25.99-1.37 27.00-0.65 28.51-0.50C36.22 0.22 45.50-6.34 56.88-15.91C57.17-16.13 57.17-16.56 56.95-16.85C56.74-17.14 56.30-17.14 56.02-16.92C49.10-11.09 44.14-7.42 39.96-5.04C35.42-2.45 31.82-1.44 28.66-1.80C27.14-2.02 27.00-2.66 26.93-2.81C26.64-3.67 26.71-5.76 30.89-11.02C33.26-14.11 35.78-16.63 35.78-16.63C36.58-17.42 43.63-24.62 43.27-31.03C43.20-33.05 42.12-34.92 40.68-36.07C39.24-37.22 37.51-38.02 35.42-38.23C33.84-38.45 32.04-38.30 30.02-37.94C26.78-37.22 24.19-36.00 24.12-35.93C20.74-34.27 16.92-31.32 12.74-27.22C9.43-23.98 5.90-20.02 2.23-15.41C-0.43-12.02-2.74-8.78-4.61-6.19C-0.50-14.76 4.97-23.47 10.22-31.10C14.47-33.91 22.54-39.31 30.67-45.50C40.90-53.35 48.02-59.98 51.98-65.30C52.06-65.45 54.79-69.26 53.14-72.72C52.63-73.73 51.70-74.59 50.62-74.95C49.32-75.38 47.81-75.24 46.15-74.45C44.71-73.73 41.33-70.70 36.94-66.02C32.04-60.91 26.42-54.43 21.24-47.95C16.92-42.55 12.89-37.22 9.29-32.04L8.93-31.54C7.34-29.16 5.76-26.86 4.25-24.55C-1.44-15.62-5.76-7.42-8.50-0.29ZM49.10-73.94C50.62-74.02 51.70-72.94 51.98-72.14C53.28-69.41 50.98-66.17 50.90-66.10C42.84-55.15 22.03-40.54 12.31-33.98C15.98-39.17 19.44-43.70 22.25-47.09C33.84-61.56 44.28-72.14 46.73-73.30C47.81-73.80 48.38-73.94 49.10-73.94ZM59.04-0.94C65.59 2.59 77.26 0 83.74-2.38C91.66-5.62 98.35-9.00 107.78-16.99C108-17.28 108.07-17.64 107.86-17.93C107.57-18.22 107.21-18.22 106.92-18.00C97.78-10.15 91.01-6.70 83.30-3.53C79.20-1.87 66.10 1.58 59.76-2.02C51.62-6.48 57.67-20.02 62.14-24.26C62.78-23.98 63.58-23.62 64.44-23.26C67.68-21.89 72.94-20.16 79.20-19.87C85.32-19.58 93.31-22.03 96.70-27.79C101.16-35.35 93.24-40.90 85.32-39.89C78.77-39.02 71.71-35.50 65.23-29.23C64.08-28.08 62.86-27.00 61.85-25.92C60.34-26.64 59.47-27.22 59.47-27.22C59.18-27.43 58.75-27.36 58.54-27.07C58.39-26.78 58.46-26.35 58.75-26.21C58.75-26.14 59.54-25.63 60.91-24.91C55.66-19.44 50.33-5.69 59.04-0.94ZM63-25.27C64.01-26.35 65.09-27.36 66.02-28.30C74.09-35.78 79.99-37.87 85.54-38.66C92.45-39.67 99.29-34.70 95.62-28.51C92.95-24.05 85.68-20.88 79.20-21.17C73.15-21.46 68.11-23.11 64.94-24.41ZM110.74 0.65C112.25 0.65 114.12 0.50 116.06-0.07C128.38-3.60 136.08-8.28 145.66-15.55C145.94-15.77 145.94-16.20 145.73-16.49C145.51-16.70 145.08-16.78 144.79-16.56C135.43-9.36 127.80-4.97 115.78-1.37C111.96-0.22 107.86-0.14 105.62-2.59C103.39-5.11 104.98-9.65 104.98-9.65C105.98-12.31 107.21-15.05 108.58-17.86C120.02-25.63 130.97-33.77 136.44-38.59C141.41-42.98 146.38-48.46 150.05-53.50C153.07-57.74 156.67-63.58 156.31-67.82C156.17-69.55 155.45-70.85 154.08-71.71C152.42-72.79 150.26-72.79 147.67-71.64C145.73-70.85 143.57-69.34 141.12-67.25C137.09-63.79 133.78-59.76 133.78-59.69L133.78-59.69C133.70-59.62 128.09-52.34 121.61-42.62C115.70-33.62 107.35-20.38 103.75-10.15C103.68-9.94 101.88-4.82 104.62-1.73C105.91-0.22 107.86 0.65 110.74 0.65ZM134.78-58.90L134.78-58.90C134.93-59.04 138.10-62.93 141.98-66.31C145.51-69.34 150.34-72.58 153.36-70.63C154.37-69.98 154.94-69.05 155.02-67.68C155.23-64.73 153.14-59.98 149.04-54.29C145.37-49.25 140.47-43.92 135.58-39.60C130.54-35.14 120.31-27.50 109.80-20.23C113.76-27.94 118.66-35.78 122.69-41.90C129.10-51.41 134.57-58.68 134.78-58.90ZM149.11 0.65C150.62 0.65 152.50 0.50 154.44-0.07C166.75-3.60 174.46-8.28 184.03-15.55C184.32-15.77 184.32-16.20 184.10-16.49C183.89-16.70 183.46-16.78 183.17-16.56C173.81-9.36 166.18-4.97 154.15-1.37C150.34-0.22 146.23-0.14 144-2.59C141.77-5.11 143.35-9.65 143.35-9.65C144.36-12.31 145.58-15.05 146.95-17.86C158.40-25.63 169.34-33.77 174.82-38.59C179.78-42.98 184.75-48.46 188.42-53.50C191.45-57.74 195.05-63.58 194.69-67.82C194.54-69.55 193.82-70.85 192.46-71.71C190.80-72.79 188.64-72.79 186.05-71.64C184.10-70.85 181.94-69.34 179.50-67.25C175.46-63.79 172.15-59.76 172.15-59.69L172.15-59.69C172.08-59.62 166.46-52.34 159.98-42.62C154.08-33.62 145.73-20.38 142.13-10.15C142.06-9.94 140.26-4.82 142.99-1.73C144.29-0.22 146.23 0.65 149.11 0.65ZM173.16-58.90L173.16-58.90C173.30-59.04 176.47-62.93 180.36-66.31C183.89-69.34 188.71-72.58 191.74-70.63C192.74-69.98 193.32-69.05 193.39-67.68C193.61-64.73 191.52-59.98 187.42-54.29C183.74-49.25 178.85-43.92 173.95-39.60C168.91-35.14 158.69-27.50 148.18-20.23C152.14-27.94 157.03-35.78 161.06-41.90C167.47-51.41 172.94-58.68 173.16-58.90ZM193.54 0.72C196.56 0.72 200.16-0.22 203.90-2.23C207.43-4.10 211.18-6.91 214.78-10.22C216.14-11.52 217.51-12.89 218.81-14.33C222.34-10.58 227.66-9.00 232.99-9.50C237.53-9.94 241.99-11.88 246.10-15.05C246.38-15.26 246.38-15.62 246.17-15.91C245.95-16.20 245.52-16.27 245.23-16.06C236.95-9.50 226.22-8.64 219.67-15.34C221.04-16.85 222.34-18.50 223.49-20.09C225.86-23.47 227.52-26.50 228.10-28.58C228.17-28.87 228.96-32.04 227.66-33.48C227.23-33.98 226.66-34.34 226.01-34.49C226.15-34.70 226.15-34.99 226.01-35.28C225.94-35.35 224.42-37.44 220.97-38.66C218.95-39.38 216.58-39.60 214.13-39.46C211.18-39.24 208.01-38.38 204.48-36.86C200.38-34.99 195.55-31.61 191.59-27.79C188.28-24.55 183.96-19.51 182.45-13.68C182.45-13.46 180.94-7.56 184.03-3.31C185.83-0.86 188.78 0.50 192.82 0.72ZM215.71-38.23C217.44-38.23 219.24-37.87 220.46-37.51C222.91-36.79 224.50-35.06 224.86-34.63C224.50-34.63 224.14-34.63 223.70-34.56C222.05-34.27 220.46-33.34 219.24-31.82C218.09-30.46 217.30-28.58 216.79-26.50C216.22-23.90 216.22-21.10 216.79-18.65C217.08-17.50 217.51-16.42 218.09-15.48C216.72-13.97 215.35-12.53 213.84-11.16C206.28-4.10 198.65-0.22 192.89-0.58C189.29-0.72 186.70-1.94 185.11-4.03C182.38-7.78 183.74-13.32 183.74-13.32C186.26-23.26 197.93-32.54 204.98-35.64C208.87-37.37 212.62-38.23 215.71-38.23ZM224.86-33.34C225.65-33.34 226.30-33.12 226.66-32.69C227.45-31.75 227.02-29.74 226.87-29.02C226.01-25.70 222.98-21.17 218.95-16.49C217.51-19.30 217.37-23.11 218.02-26.21C219.02-30.67 221.54-33.26 224.86-33.34Z"
/>
</svg>
Now, obviously no one can draw like that (unless they're an octopus).
The issue is with the path, but I cannot find an alternative way to achieve this. Inkscape, perhaps? Anyway, the path is generated by: https://www.npmjs.com/package/text-to-svg, using the League Script font from: http://www.quillingpatch.com/2015/01/31/list-of-free-single-line-fonts-for-rhinestones-pensmarkers/. text-to-svg does the job, but it doesn't suit this purpose. Some other issues:
League Script is supposed to be a single line font, but it seems that it uses fill. I've looked into Hershey fonts, engraving fonts, yet I haven't found something that would generate a path appropriate for animated drawing.
path.getTotalLength returns a length that is much longer than the actual length as the drawing is completed much earlier than when length reaches 0. Most likely caused by the way the path is.
The path needs to be translated by arbitrary values as it contains negative values (as in the CSS above). Would there be a better way to fix this than by parsing through the data to find the lowest negative X and Y values and then translating it by that amount?
Suggestions?
As you have said, the font you are using only appears to be a single line. But it is actually not. The paths in the characters go around the boundary of the font glyphs
This will be true of all fonts. So any text-to-svg utility will not work for you.
You will almost certainly have to convert the SVG to the suitable format yourself. Load your converted SVG into Inkscape (or your favourite vector editor) and manually trace the shape of each of your letters with single (non-filled) paths.
You will then have an SVG that will work with dash-offset animation.
Related
I have a project in which I have a long SVG path. The SVG path is centered on the page and has a stroke "none" so that it is invisible. Then I have an image of a rocket. The idea is that this rocket should follow the path on scroll. Meaning for each pixel scrolled, it moves one pixel into the direction of the line.
This so far works great. I got the idea for my code from this stackoverflow question.
My problem now is the specific path I created for this. The path has sections in which the path turns sideways or even up (for a looping). The rocket should still follow in these sections, which it does as you can see on the live example here. But when moving through the looping the rocket leaves the view, because it is still moving at the same speed like the scrolling, but just away from the users scroll direction.
Now I would like to speed up the rocket when it is moving upwards through the looping so it doesn't leave out of view. Let's look at my code.
The Project is made in sveltekit, but that is not important right now because for my problem only the JS should be relevant. So for that matter here is the HTML:
<div style="position:absolute; top:0; right:0; left:0; z-index:1000; pointer-events: none;" id="route">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 966 4692" id="svgRoute">
<path id="path" d="M652 0V239.5V317.5C538.333 325.5 312.9 402.4 320.5 646C328.1 889.6 474.667 970.5 547 980.5C573.667 983 632 988.7 652 991.5C677 995 806 993 798 1118.5C791.6 1218.9 628 1275.67 547 1291.5C372.5 1294.67 30.1999 1323.2 56.9999 1412C90.4999 1523 171.5 1565.5 293.5 1607C415.5 1648.5 707 1657 786.5 1931C850.1 2150.2 436.667 2111 222 2064C64.5001 2047.5 -163.9 2094.8 182.5 2416C528.9 2737.2 800.5 2670.5 893 2597C984.5 2452 1061.5 2189 637.5 2297C213.5 2405 390.167 2773.67 531.5 2944.5C665.833 3062 924 3334.1 882 3482.5C829.5 3668 442 3885.5 420 3990C398 4094.5 764.5 4537 774 4691" stroke-width="5" stroke="red" fill="none"/>
<image id="c" x="-20" y="-70" width="40" height="70" xlink:href="data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF4AAACqCAYAAAAk0fhhAAAFP0lEQVR4nO2dzWtcZRSH36tjGKdNOkOSFpqk+RAtqVGEtGahWLQQF0JMKBhQqKjVnVu7dK3gqiC4qGDAhQtpCC4kEOgsKooWQWuDYD9i81EH25RGrUlTx3/gnIvvzXWeKfk9y9+de8+dh7M4vPcrqdfrodlIkiTPk0qskP7f96HVtzESDyHxEBIPIfEQEg+RkGOVNzaOjR/LrcbM9JRb3tvQCCfqeAiJh5B4CImHkHiIQiOKxE4vn53+JLfakxN2PjM95Y4uSZL87wtr6ngIiYeQeAiJh5B4iFzXavKaXqpnvndrPLKv1cx79naa+WZxl5lPTrzq1ohd38niUB0PIfEQEg8h8RASDyHxENHjZNpdXnmNjW++9pZb/4Xxl8z8nbft1TBvzCwUS26NFyeOm3meY6Y6HkLiISQeQuIhJB4i10t/vV3tZp5levFY/u2Gmb9/8rSZe9NO/0CvW8P7H3mijoeQeAiJh5B4CImHaMgNTR4rK9fMfOixx6OP5U07zYo6HkLiISQeQuIhJB4CnWo8zv/4g7tt38PxE08zoo6HkHgIiYeQeAiJh5B4iIaMk+X2DjP/4suZ6GN598ffvLZs5qWW5uyt5jyrbYDEQ0g8hMRDSDxEQ6aa7p4uM+/YvcfMdxY23WO1ljbMvFqzb7su3vnHzO3KjUMdDyHxEBIPIfEQEg+BXvq7urBg5qWif1qd5fvN/OLyX2Y+tKsl/sQagDoeQuIhJB5C4iEkHiLXqWZ17U6ehzO5e/ummb/7rH1laj0pmvmvi7+7NRrxP9TxEBIPIfEQEg8h8RASD+GOk1m+t7ejtbK1s9kCV2prZj7QZ5/TqQ/tdx+EEMJPv9Riy5uuvNehh6COx5B4CImHkHgIiYeIXiTr6Xva3bZnb/+WTua/0NkR9/akv9ftG6CycGTUfq/m3Kz7PkoXdTyExENIPITEQ0g8RMFbkxl+cszcoVa7t96ElCdjky9H/X5u1v+snToeQuIhJB5C4iEkHiJ6raa1Yj9Ith3o7rK/N5IFdTyExENIPITEQ0g8hMRDoE/9tZXtm42KD6Ts05ayMScOHhox86WV67nVUMdDSDyExENIPITEQ7hTTblcNvNHB/zXiJ+tnonKb7Vwt3W3baxG7/PUc8/nVl8dDyHxEBIPIfEQEg8RvVazdMn/cEr17Ndm3ldfMvPjz9hvf2wZtCeqEEJ4sP8hM799+aKZb8zb7z746Cv/XQa3KvvNfMfUSXefWNTxEBIPIfEQEg8h8RDuI9/BeUw8C68fHDTzU684O4ymPMR2YMDOL1yy89nLZvzGp36Jj7+b9zdGcGT0mB6pbzYkHkLiISQeQuIhJB7CXSRLG4U80p5yi2F1wf6gSgghVA7E7ZPnxcX2nkOmkycG7XE5DXU8hMRDSDyExENIPEShXrcHkZRXJqKsXbAv5TWC61e/NfM5J09DHQ8h8RASDyHxEBIP4a7VeNNOGt4kdONP+zNxIdyNrrG54N+IFIN/TvFkcaWOh5B4CImHkHgIiYdAH6n/5vPdZj5y1P7AbgghrO7vNfPKz/Y+Xo0Q8ns8PgvqeAiJh5B4CImHkHgIiYdIsizwuAfzLxeaRQ53D5s/fq9vp1tj5Kj9OThvbDxx5Q8zry6ec2uMDw6Zf2R6/rz5ey2S3UNIPITEQ0g8hMRDNGSRzJ8SzpnjwIlgTzshhBA+KDkb4qaXw93DKXdsrfubckIdDyHxEBIPIfEQEg+R61qNW8RZw/HWaqqL9rSThfTpxcabhPJ0pY6HkHgIiYeQeAiJh5B4iH8BrZ8Mhx0ulsYAAAAASUVORK5CYII="/>
</svg>
</div>
The div is id "route" and will be called as such multiple times throughout the JS. The <path> here is the line stretching through the whole page. The <image> is the rocket which I placed directly inside the SVG. The only relevant css is inside the style tag for the div.
Now for the JavaScript/jQuery
jQuery(document).ready(function(){
jQuery(document).scroll(function() {
positionCar();
});
// init the line car position
positionCar();
function positionCar() {
var scrollY = jQuery('#route').scrollTop() || window.pageYOffset;
var maxScrollY = jQuery('#route').prop('scrollHeight');
var path = document.getElementById("path");
// Calculate distance along the path the car should be for the current scroll amount
var pathLen = path.getTotalLength();
var dist = pathLen * scrollY / maxScrollY;
var pos = path.getPointAtLength(dist);
// Calculate position a little ahead of the car (or behind if we are at the end), so we can calculate car angle
var angle = calculateAngle(path, pathLen, dist, pos)
if (angle < 0) {
var pathLen = path.getTotalLength();
var dist = pathLen * scrollY / maxScrollY;
var pos = path.getPointAtLength(dist);
var angle = calculateAngle(path, pathLen, dist, pos)
}
// Position the car at "pos" totated by "angle" (if angle is upwards position further for more speed)
var car = document.getElementById("c");
car.setAttribute("transform", "translate(" + (pos.x) + "," + (pos.y) + ") rotate(" + (rad2deg(angle) + 90) + ")");
}
function calculateAngle(path, pathLen, dist, pos) {
if (dist + 1 <= pathLen) {
var posAhead = path.getPointAtLength(dist + 1);
return Math.atan2(posAhead.y - pos.y, posAhead.x - pos.x);
} else {
var posBehind = path.getPointAtLength(dist - 1);
return Math.atan2(pos.y - posBehind.y, pos.x - posBehind.x);
}
}
function rad2deg(rad) {
return 180 * rad / Math.PI;
}
});
So jQuery checks if document ready and binds a scroll handler which then calls the positionCar() function everytime a scroll happens to place the rocket accordingly. The function basically calculates the position off the car on the path line by scroll position and path length. Then it uses the calculateAngle function to check for the required angle ahead of the path.
The angle is used so the rocket follows the path an turns in the direction of the path. Then the rocket (here called car) is positioned at that position in that specific angle.
The if condition inside the positionCar() function for now does nothing different. It checks if the angle is below zero indicating the path going upwards (for example the looping). My idea now is to implement more speed inside this condition because it tells when the rocket is moving back up instead of down.
How would I accomplish this. I tried playing with the calculations but it just resulted in jumping around. By changing the dist variable I managed to get the rocket faster during upwards movement. This just resulted in the rocket jumping back as soon as the path moves back down. How would I need t implement faster rocket speed only during the upwards movement?
For playing around with it I created a REPL with the full code here: https://svelte.dev/repl/d2e449585ac14ec8aef1fd310fb14548?version=3.46.4
I want to rotate the compass image according to the degrees I receive. The input degrees are between 0 and 359 so when I rotate -1 degree from 0 , the image rotates one complete round and sets on 359:
function setCompass(degrees){
$("#compass").stop().transition({rotate: -1*degrees +'deg'});
}
I tried to solve the problem by going back to -1 instead of 359 so I changed the code to the following:
function setCompass(degrees){
if (degrees>180){degrees=degrees-360}
$("#compass").stop().transition({rotate: -1*degrees +'deg'});
}
It solved the lag on 0 and 360 but the problem was shifted to 180. Now when I rotate the device from 180 to 181, it rotates a complete negative round to go back to -179 ! Please suggest me how to modify the calculations so I get smooth changes on every degree?
You could use some modulo formula to find out whether to turn clockwise or anti-clockwise. Persist the current angle with jQuery's data method (so you could have more than one such compass if you wanted to), and allow the angle that you pass as CSS value to be outside the 0-359 range.
Here is how it could work:
function setCompass(degrees){
var curr = $("#compass").data("rotation") || 0;
var next = curr + ((degrees - curr)%360 + 180*3) % 360 - 180;
$("#compass").data("rotation", next).stop().transition({rotate: -next + 'deg'});
}
// Demo with randomly generated angles between 0 and 359
setInterval(function() {
setCompass(Math.floor(Math.random()*360));
}, 1000);
<img id="compass" src="https://i.stack.imgur.com/zdHVn.png" width="100px" height="100px">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.transit/0.9.12/jquery.transit.min.js"></script>
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 + ')');
}
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)
}
}
};
There are many examples of SVG path animation, both natively
http://jsfiddle.net/FVqDq/
and with Raphael.js
http://jsfiddle.net/d7d3Z/1/
p.animate({path:"M140 100 L190 60"}, 2000, function() {
r.animate({path:"M190 60 L 210 90"}, 2000);
});
How is this possible with the svg.js library?
No, this is not yet possible with svg.js. I have been looking into it and it will be a rather large implementation. As I try to keep the library small it will never be part of the library itself, but I might write a plugin. Although at the moment I do not have much time on my hands so all help will be appreciated.
UPDATE:
This is now possible with SVG.js out of the box if you use paths with equal commands but different values.
But we also have a path morphing plugin for SVG.js which is probably the thing you are looking for.
There is a quick and dirty way to animate a line with svg.js:
http://jsfiddle.net/c4FSF/1/
draw
.line(0, 0, 0, 0)
.stroke({color: '#000', width: 2})
.animate(1000, SVG.easing.bounce) // Using svg.easing.js plugin(not required)
.during(function(t, morph) {
this.attr({x2:morph(0, 100), y2: morph(0, 100)})
})
Animating complex SVG paths as wout said will require a plugin.
Unfortunately I don't (yet) know enough about SVG, but I'm thinking of writing a plugin which would use the SMIL animation tag. Which is what is used in the first link of the question.
We can make path animation by finding the bounding box of your path and the do like this.
if your path having some clipping -rectangle means like that below
<g id="container_svg_SeriesGroup_0" transform="translate(128.8,435)" clip-path="url(#container_svg_SeriesGroup_0_ClipRect)"><path id="container_svg_John_0" fill="none" stroke-dasharray="5,5" stroke-width="3" stroke="url(#container_svg_John0Gradient)" stroke-linecap="butt" stroke-linejoin="round" d="M 0 -17.25 L 21.7 -112.12499999999999 M 21.7 -112.12499999999999 L 43.4 -51.75 M 43.4 -51.75 L 86.8 -25.875 M 86.8 -25.875 L 108.5 -155.25 "/><defs><clipPath id="container_svg_SeriesGroup_0_ClipRect"><rect id="container_svg_SeriesGroup_0_ClipRect" x="0" y="-155.25" width="118.5" height="148" fill="white" stroke-width="1" stroke="transparent" style="display: inline-block; width: 118.5px;"/></clipPath></defs></g>
var box = $("#"+ path.id")[0].getBBox();
create the rectangle based on the box and the set this rectangle as your clip-path in path.
then increase the width of the rectangle step by step in jquery.animate.
doAnimation: function () {
//cliprect is your clipped rectangle path.
$(clipRect).animate(
{ width: 1000},
{
duration: 2000,
step: function (now, fx) {
$(clipRect).attr("width", now);
}
});
},
jquery.animate step function is used to increase the width of your clip-rect step by step.
You can animate paths using the svg.path.js plugin.
See the first examples (using the .drawAnimated method).
Another option, which we've resorted to, is to use textPath and then use a character.
In our case we're using the • entity, but I'm thinking if you create your own typography in .svg, .woff etc, you can have flat shapes of any kind.
So you would use your character as in here:
http://jsfiddle.net/wbx8J/3/
/* create canvas */
var draw = SVG('canvas').size(400,400).viewbox(0, 0, 1000, 1000)
/* create text */
var text = draw.text(function(add) {
add.tspan('•').dy(27)
})
text.font({ size: 80, family: 'Verdana' })
/* add path to text */
text.path('M 100 400 C 200 300 300 200 400 300 C 500 400 600 500 700 400 C 800 300 900 300 900 300')
/* visualise track */
draw.use(text.track).attr({ fill: 'none'/*, 'stroke-width': 1, stroke: '#f09'*/ })
/* move text to the end of the path */
function up() {
text.textPath.animate(3000).attr('startOffset', '100%').after(down)
}
/* move text to the beginning of the path */
function down() {
text.textPath.animate(3000).attr('startOffset', '0%').after(up)
}
/* start animation */
up()