// get canvas
var canvas = document.getElementById("can");
var ctx = canvas.getContext("2d");
// regexp for parsing path
var mark = /([MmLlQqSsHhVvCc])/g;
var spaces =//g;
var space2Comma =//g;
var neg = /[0-9]-/g;
var noZ = /Z/gi;
const PRECISION = 0.1; // smaller numbers make better fit.
var path = "M0.464,59.322c0,0,35.468-88.67,101.478-48.276 s72.906,85.547,44.827,136.123s-70.443,67.817-101.97,81.118";
// Get point on cubic
var getPointOnBezierCurve = function(x1, y1, x2, y2, x3, y3, x4, y4, p, point){
if(point === undefined){
point = {x : null, y : null};
}
var xx1 = (x2 - x1) * p + x1;
var yy1 = (y2 - y1) * p + y1;
var xx2 = (x3 - x2) * p + x2;
var yy2 = (y3 - y2) * p + y2;
var xx3 = (x4 - x3) * p + x3;
var yy3 = (y4 - y3) * p + y3;
var xxA1 = (xx2 - xx1) * p + xx1;
var yyA1 = (yy2 - yy1) * p + yy1;
var xxA2 = (xx3 - xx2) * p + xx2;
var yyA2 = (yy3 - yy2) * p + yy2;
point.x = (xxA2 - xxA1) * p + xxA1;
point.y = (yyA2 - yyA1) * p + yyA1;
return point;
}
// Get point on quad
var getPointOnBezier2Curve = function(x1, y1, x2, y2, x3, y3, p, point){
if(point === undefined){
point = {x : null, y : null};
}
var xx1 = (x2 - x1) * p + x1;
var yy1 = (y2 - y1) * p + y1;
var xx2 = (x3 - x2) * p + x2;
var yy2 = (y3 - y2) * p + y2;
point.x = (xx2 - xx1) * p + xx1;
point.y = (yy2 - yy1) * p + yy1;
return point
}
// get length of a line
function getLineLength(){
var n = this.nums;
return Math.sqrt(Math.pow(n[0] - n[2], 2) + Math.pow(n[1] - n[3], 2));
}
// get length of a bezier quad
function getB2Length(){
var n = this.nums, i, p, p1, len;
p = {x : n[0], y : n[1]};
p1 = {x : n[0], y : n[1]};
len = 0;
for(i = PRECISION; i <= 1; i += PRECISION){
p1 = getPointOnBezier2Curve(n[0], n[1], n[2], n[3], n[4], n[5], i ,p1);
len += Math.sqrt(Math.pow(p1.x - p.x, 2) + Math.pow(p1.y - p.y, 2));
log(len)
p.x = p1.x;
p.y = p1.y;
}
return len;
}
// get length of a cubic bezier
function getB3Length(){
var n = this.nums, i, p, p1, len;
p = {x : n[0], y : n[1]};
p1 = {x : n[0], y : n[1]};
len = 0;
for(i = PRECISION; i <= 1; i += PRECISION){
p1 = getPointOnBezierCurve(n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7], i, p1);
len += Math.sqrt(Math.pow(p1.x - p.x, 2) + Math.pow(p1.y - p.y, 2));
p.x = p1.x;
p.y = p1.y;
}
return len;
}
// get a point on a line
function pointOnLine(p, point){
if(point === undefined){
point = {x : null, y : null};
}
point.x = (this.nums[2] - this.nums[0]) * p + this.nums[0];
point.y = (this.nums[3] - this.nums[1]) * p + this.nums[1];
return point;
}
// get point on bezier cubic
function pointOnB2(p, point){
var n = this.nums;
return getPointOnBezier2Curve(n[0], n[1], n[2], n[3], n[4], n[5], p, point);
}
function pointOnB3(p, point){
var n = this.nums;
return getPointOnBezierCurve(n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7], p, point);
}
// not included V,H, and whatever arc is
var types = {
"M":{numbers : 2},
"L":{numbers : 2 , func : pointOnLine, lenFunc : getLineLength},
"Q":{numbers : 4 , func : pointOnB2, lenFunc : getB2Length},
"C":{numbers : 6 , func : pointOnB3, lenFunc : getB3Length},
"S":{numbers : 4},
"T":{numbers : 2},
}
function getPointOnPath(pos, point){
var i = 0;
while(i < this.length && !(this[i].startLength <= pos && this[i].startLength + this[i].length >= pos)){
i += 1;
}
if(i < this.length){
return this[i].getPoint((pos - this[i].startLength)/this[i].length, point);
}
return undefined;
}
// function to parse path string
function parsePath(path){
var parts, newPath, i, seg, lseg, len;
try{
// Format path for easy parsing
path = path.replace(noZ, ""); // remove the Z I am just ignoring it
path = path.replace(spaces, " "); // remove any excess spaces
path = path.replace(neg, ",-"); // insert commas if neg follows a number
path = path.replace(space2Comma, ","); // convert spaces to commas
// Split into segments
parts = path.replace(mark, "#$1").substr(1).split("#");
// parse each sement add to the new path
newPath = [];
parts.forEach(function(p){
var i, nums, type, seg;
// get the numbers
nums = p.substr(1).split(",");
// get the type as uppercase
type = types[p[0].toUpperCase()];
// create a segment
seg = {
type : p[0].toUpperCase(),
nums : [],
rel : false,
}
// check if relative
if(p[0] === p[0].toLowerCase()){
seg.rel = true;
}
// read the requiered numbers
for(i = 0; i < type.numbers; i++){
seg.nums.push(Number(nums[i]));
}
// add the new path segment
newPath.push(seg);
});
// convert relative path coords to absolute
newPath.forEach(function(seg, i){
var j, x, y, xx, yy;
if(i !== 0){
xx = x = newPath[i-1].nums[newPath[i-1].nums.length-2];
yy = y = newPath[i-1].nums[newPath[i-1].nums.length-1];
if(seg.rel){
for(j = 0; j < seg.nums.length; j+= 2){
seg.nums[j] += x;
seg.nums[j + 1] += y;
}
}
// Add the start of the segment so that they can be handled
// without the need to reference another seg
if(seg.type !== "M"){
seg.nums.unshift(yy)
seg.nums.unshift(xx)
}
}
});
// Convert S an T path types to C and Q
// Also remove M commands as they are not needed
// also Calculate length of each seg NOTE bezier lengths are estimates only
len = 0;
for(i = 0; i < newPath.length; i++){
seg = newPath[i]
if(seg.type === "M"){
newPath.splice(i, 1);
i --;
}else{
if(seg.type === "S"){
seg.type = "C";
lseg = newPath[i - 1];
if(lseg.type === "C"){
seg.nums.splice(2, 0, seg.nums[0] - (lseg.nums[4] - lseg.nums[6]), seg.nums[1] - (lseg.nums[5] - lseg.nums[7]));
}else{
// Missing CODE STUB
}
}else
if(newPath.type === "T"){
seg.type = "Q";
lseg = newPath[i - 1];
if(lseg.type === "Q"){
seg.nums.splice(2, 0,seg.nums[0] + (lseg.nums[2] - lseg.nums[4]), seg.nums[1] + (lseg.nums[3] - lseg.nums[5]));
}else{
// Missing CODE STUB
}
}
// add function to find point
seg.getPoint = types[seg.type].func.bind(seg);
// set start pos an calculate length
seg.startLength = len;
len += seg.length = (types[seg.type].lenFunc.bind(seg))();
}
}
// set total calculated length
newPath.totalLength = len;
// add getPoint function binding to newPath
newPath.getPoint = getPointOnPath.bind(newPath);
return newPath;
}catch(e){
throw new ReferenceError("Something not so good parsing path.")
}
}
// Path the path. Sorry code is real rush job from here
var p = parsePath(path);
ctx.lineJoin = "round";
ctx.lineCap = "round";
var pp = new Path2D(path); // use standard path to show that I am following correctly
var t = 0
var pt = {x : 0,y : 0};
function update1(){
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(1, 0, 0, 1, 100, 100);
ctx.lineWidth = 4;
ctx.strokeStyle = "#FF8";
ctx.stroke(pp);
ctx.strokeStyle = "#000";
t = (t + 1) % p.totalLength;
ctx.beginPath();
pt = p.getPoint(t % p.totalLength, pt);
ctx.moveTo(pt.x, pt.y);
for(var k = 0; k < 100; k += 4){
var ppt = p.getPoint(t + k, pt);
if(ppt !== undefined){
ctx.lineTo(ppt.x, ppt.y);
}
}
ctx.stroke();
requestAnimationFrame(update1);
}
update1()
.canC {
width:500px;
height:500px;
}
<canvas class= "canC" id="can" width = 500 height = 500></canvas>
дорогой слепой67 ты бог – Hiero