2016-01-06 3 views
0

Я рисую этот 2D-контур в контексте холста.Линия самофиксации на холсте

http://jsbin.com/paroximebe/edit?js,output

var d = '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'; 
var p = new Path2D(d); 

context.lineWidth = 2; 
context.lineJoin = context.lineCap = 'round'; 
context.strokeStyle = '#000000'; 
context.translate(100, 100); 
context.stroke(p); 

Дело в том, что я хочу достичь в том, что, когда пользователь нажимает на холст, я хочу, чтобы линия, чтобы быть само обращается (анимированный), аналогично с this эффектом.

Может кто-нибудь объяснить мне, как это достичь?

Спасибо!

ответ

3

Нет простого решения. Я искал парсер Path, но ничего не нашел, поэтому должен был написать его. Извините за плохое качество, но меня толкают на время и не могут предоставить полное решение.

То, что я сделал это разобрать путь, а затем возвращается объект, который имеет путь в форме, вы можете получить точку длины п

Функция parsePath(path) где path является строка пути. Он возвращает новый объект пути, который имеет свойство totalLength, которое является приблизительной длиной пути в пикселях, и имеет метод getPoint(pos,{x:0,y:0});, который возвращает точку {x:?,y:?} в качестве координат X и Y на пути для позиции pos вдоль пути. pos = 0 начало пути, pos = path.totalLength - конец пути. Значения вне пути return undefined Второй аргумент является необязательным, но лучше всего предоставить точку, чтобы остановить перегрузку GC. parsePath() будет бросать ссылкуError, если что-то пойдет не так.

Использование

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"; 

var pPath = parsePath(path); 
console.log(pPath.totalLength); 
// get a point half way along the path 
var point = pPath.getPoint(pPath.totalLength/2); 
console.log("Mid X coordinate:" + point.x) 
console.log("Mid Y coordinate:" + point.y) 

parsePath() Функция не делает горизонтальных и вертикальных линий, а также не делать S,s,T,t путь команды, когда предыдущий отрезок пути не одного типа. Я не мог сработать в то время, когда у меня было то, что должно было случиться. Вы можете поместить этот код, поскольку я оставил для него заглушки с комментарием // Missing CODE STUB

Я тестировал его только на вашем пути.

В нижней части кода я живу, как вы хотите «Я угадываю» Потому что нелегко разрезать безье на части, я просто проецирую путь каждые 4 пикселя, а затем рисую линии между ними. Это только приближение. Демо рисует путь, используя стандартный объект Path2D, а затем нарисовывает его с помощью анализируемой информации о пути, чтобы дать вам возможность судить, приемлемо ли качество.

Также скорость не постоянна. Было бы намного больше кода, чтобы скорость на beziers была постоянной, извините, но у меня нет времени прямо сейчас, чтобы исправить это. Я вернусь к нему, если у меня будет больше времени.

И последнее извинение за плохой синтаксис кода, очистит его, когда я получу шанс.

Это ТОЛЬКО как пример того, как решить проблему, и НЕ является полным решением, я не ожидал, что он будет настолько сложным и собирался сбрасывать его, но есть достаточно, чтобы ответить на вопрос, поэтому отходы не хочет нет.

// 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>

+0

дорогой слепой67 ты бог – Hiero

0

Я считаю, что в этом случае вам придется рисовать анимацию кадр за кадром. Итак, в основном нарисуйте часть линии, тайм-аут на несколько мс, нарисуйте еще одну часть кадра, ...

+0

да, но вопрос в том, как получить лишь небольшую часть строки из этих данных? – Hiero

Смежные вопросы