簡體   English   中英

畫布上的自畫線

[英]Self drawing line on Canvas

我在畫布上下文上繪制此2D路徑。

http://jsbin.com/paroximebe/edit?js,輸出

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

我要實現的事情是,當用戶單擊畫布時,我希望線條可以自繪制(動畫),與效果類似。

有人可以解釋我如何實現這一目標嗎?

謝謝!

沒有簡單的解決方案。 我在尋找路徑解析器,但沒有發現任何東西,只好寫一個。 抱歉,質量很差,但是我被抽空了,無法提供完整的解決方案。

我所做的是解析路徑,然后返回一個對象,該對象的路徑形式為可以獲取長度為n的點

該函數是parsePath(path) ,其中path是路徑字符串。 它返回具有屬性totalLength的新路徑對象,該屬性為路徑的大約長度(以像素為單位),並具有方法getPoint(pos,{x:0,y:0}); 它返回點{x:?,y:?}作為路徑上位置pos的路徑上的X和Y坐標。 pos = 0路徑的開始, pos = path.totalLength是路徑的結束。 路徑外的值返回undefined第二個參數是可選的,但最好提供一個點來阻止GC過度工作。 如果出現任何問題, parsePath()將拋出referenceError。

采用

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)

當先前的路徑段不是同一類型時S,s,T,t函數parsePath()不會執行水平和垂直線,也不會執行S,s,T,t路徑命令。 我無法應付應該發生的事情。 您可以將該代碼放入,因為我在代碼中留下了存根並帶有注釋// Missing CODE STUB

我只在您的路徑上進行過測試。

在代碼的底部,根據需要進行動畫處理“我正在猜測”,因為將貝塞爾曲線切成部分並不容易,因此我僅每4個像素對路徑進行采樣,然后在它們之間繪制線條。 這只是一個近似值。 該演示使用標准的Path2D對象繪制路徑,然后使用解析的路徑信息在其上繪制,從而為您提供一種判斷質量是否可接受的方法。

而且速度不是恆定的。 很抱歉,將需要更多代碼來使bezier的速度保持恆定,但我現在沒有時間解決此問題。 如果我有更多的時間,我會再來的。

對於不好的代碼語法,最后的道歉將在我有機會的情況下解決。

這僅是作為解決問題的一個示例,並不是一個完整的解決方案,我沒想到它會如此復雜並打算將其轉儲,但是有足夠的答案可以解決這個問題,所以不要浪費。

 // 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 - px, 2) + Math.pow(p1.y - py, 2)); log(len) px = p1.x; py = 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 - px, 2) + Math.pow(p1.y - py, 2)); px = p1.x; py = 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 ZI 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> 

我相信在這種情況下,您將必須逐幀繪制動畫。 所以基本上畫了一部分線,超時了幾毫秒,畫了框架的另一部分,...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM