简单贝塞尔曲线实现 - Javascript大作业
贝塞尔曲线:
似乎是在Windows XP的屏幕保护选项里面看到过贝塞尔曲线,一直对这个名字比较感兴趣,刚好最近想起来了便百度了一下。
参考:贝塞尔曲线扫盲 是当时第一次看的,讲的挺通俗易懂的;Wiki: wikipedia-Bézier curve ;绘制算法: de Casteljau's 算法
学习过Photoshop,对里面的钢笔工具印象颇深,看到钢笔工具用的就是贝塞尔曲线的时候内心也是激动不已。
wiki中给出的数学公式还是蛮考研耐心的,不过后面的那个算法还是很容易描述的,大致思路:
生成点
枚举 0.001 - 1.000 的每个 T,获得T值对应的坐标:
将生成的点集合从前到后依次连线
在每条线段的t值处取新点
将新的点集作为点集合重复上述操作直到只剩下一个点
绘制剩下的这个点
其中的T值实际上就是一个位置,更具体的信息可以参考链接中的描述。
使用HTML5的canvas,通过JavaScript实现上述算法:
大致思路上面也说过了,具体的话就是使用canvas的fillrect来绘制点了
另外,由于JavaScript本身是单线程的,而我的程序需要一个死循环(随机嘛,不停地画曲线~),因此需要使用Worker进行多线程操作,它的逻辑就更简单了,主线程:
1 var worker = new Worker("caculatePoint.js");
2 worker.postMessage(data);
1 onmessage = function startHere(event) {
2 ...
3 postMessage(data);
4 }
看起来应该是相当易懂了。主线程通过另一个js文件创建新线程,发消息启动子线程,子线程发消息通知主线程。
实际效果(一次画6条贝塞尔曲线(尾首相连)然后清空画布):
JavaScript作业:
作业没什么好说的,主要是要画执行模型和对象模型的内存图,已经贴出,欢迎指正
需要注意的是,使用引入的iframe以及JavaScript时最好是在搭建好的服务器上运行,不然使用chrome浏览器运行时可能会出现错误,不过用ie,edge以及Firefox时没问题的。
由于贝塞尔曲线部分是JavaScript作业的一部分,因此详细代码包括 两个HTML文件,两个JS文件,一个css文件和几个图标。代码如下:
main.html:
1 <!DOCTYPE html> 2 <html lang=zh-cmn-Hans> 3 4 <head> 5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 6 <title>20152480227_SJF</title> 7 <link rel="stylesheet" type="text/css" media="screen" href="main.css" /> 8 <script src="main.js"> 9 </script> 10 </head> 11 12 <body style="background: whitesmoke;background: url(bg_partical.jpg)"> 13 <div style="float: left;"> 14 <div> 15 <iframe src="iframe.htm" id="myiframe" class="my_iframe"> 16 </iframe> 17 </div> 18 <div> 19 <textarea id="myinput" class="my_text_area"></textarea> 20 </div> 21 <div class="my_bot_button"> 22 <input type="radio" checked name="myradio" value="add" onclick="setOKButtonText('添加')" />添加 23 <input type="radio" name="myradio" value="replace" onclick="setOKButtonText('替换')" />替换 24 <input type="radio" name="myradio" value="delete" onclick="setOKButtonText('删除')" />删除 25 <input type="radio" name="myradio" value="insert" onclick="setOKButtonText('插入')" />插入 26 <select id="myselect"> 27 <option value="0">0</option> 28 <option value="1">1</option> 29 <option value="2">2</option> 30 <option value="3">3</option> 31 </select> 32 <input type="submit" value="添加" id="myokbutton" onclick="goHere()"> 33 </div> 34 <br /> 35 <br /> 36 <br /> 37 </div> 38 <div class="my_right_canvas" id="mycanvasdiv" ondblclick="changeCanvasVisibility()"> 39 <canvas id="canvas" width="850" height="720" style="background: white"></canvas> 40 <script> 41 var width = 850, height = 720; 42 canvas = document.getElementById("canvas"); 43 ctx = canvas.getContext("2d"); 44 ctx.fillStyle = "#FF0000"; 45 46 var cnt = 0; 47 var worker = new Worker("caculatePoint.js"); 48 worker.postMessage(''); 49 worker.addEventListener('message', function (e) { 50 if (cnt != e.data.cnt) { 51 ctx.fillStyle = "#" + parseInt(Math.random() * 256).toString(16) + 52 parseInt(Math.random() * 256).toString(16) + 53 parseInt(Math.random() * 256).toString(16); 54 } 55 ctx.fillRect(e.data.x, e.data.y, 3, 3); 56 if (cnt != e.data.cnt && e.data.cnt % 6 == 0) { 57 ctx.fillStyle = "#FFFFFF"; 58 ctx.fillRect(0, 0, width, height); 59 ctx.fillStyle = "#ff0000" 60 } 61 cnt = e.data.cnt; 62 }, false); 63 </script> 64 </div> 65 </body> 66 67 </html>
iframe.html
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <link rel="stylesheet" type="text/css" media="screen" href="main.css" /> 5 </head> 6 <body class="my_iframe_body" id="myiframebody"> 7 <p style="font-size: 28px;font-family: Jokerman, 汉仪力量黑简; color: rgb(165, 25, 25)"> 8 JS Homework.<br/>JavaScript 大作业 9 </p> 10 <p> 11 You can choose to add, replace, delete or insert one paragraph when you have input your text. 12 <br/>增删改查随便选~ 13 </p> 14 <p> 15 Make sure you choose the right place if you do insertion. 16 <br/> 插入段落时确保你已经选择了正确的序号~ 17 </p> 18 <p> 19 Empty input will be ignored! 20 <br/>不输入任何字符将不会产生任何操作! 21 </p> 22 </body> 23 24 </html>
main.js
1 var myifbody; 2 3 4 /** 5 * 启动时获取iframe的body 6 */ 7 window.onload = function init() { 8 myifbody = document.getElementById("myiframe").contentDocument.body; 9 } 10 11 /** 12 * 将 {@link #myokbutton} 的值替换为 s 13 * @param {string} s 待替换的字符串 14 */ 15 function setOKButtonText(s) { 16 document.getElementById("myokbutton").setAttribute("value", s); 17 } 18 19 /** 20 * 更改容纳canvas的div的visibility属性 21 */ 22 function changeCanvasVisibility() { 23 canvasdiv = document.getElementById("canvas"); 24 console.debug("double here"); 25 if (canvasdiv.style.visibility == "hidden") { 26 canvasdiv.style.visibility = "visible"; 27 } 28 else { 29 canvasdiv.style.visibility = "hidden"; 30 } 31 } 32 33 /** 34 * 点击 {@link #myokbutton} 时执行此函数 35 */ 36 function goHere() { 37 // text area 38 s = document.getElementById("myinput").value; 39 40 //radio input, know what to do(append, replace, delete, insert) 41 myradios = document.getElementsByName("myradio"); 42 cho = 0; 43 for (cho; cho < myradios.length; cho++) { 44 if (myradios[cho].checked) 45 break; 46 } 47 //ignore empty string 48 if (s.length <= 0 && cho != 2) 49 return; 50 //clear text area 51 document.getElementById("myinput").value = ""; 52 ind = document.getElementById("myselect").selectedIndex; 53 54 switch (cho) { 55 case 0: 56 doAppend(s); 57 break; 58 case 1: 59 doReplace(ind, s); 60 break; 61 case 2: 62 doDelete(ind, s); 63 break; 64 case 3: 65 doInsert(ind, s); 66 break; 67 default: 68 break; 69 } 70 } 71 72 /** 73 * 将序号为ind的元素的innertext替换为s 74 * @param {number} ind 待替换的HTMLElement序号 75 * @param {string} s 用来替换的新字符串 76 */ 77 function doReplace(ind, s) { 78 myifbody.getElementsByTagName("p")[ind].innerText = s; 79 } 80 81 /** 82 * 在iframe.body的ind序号处插入一个内容为s的p标签 83 * @param {number} ind 待插入的位置 84 * @param {string} s 待插入的字符串 85 */ 86 function doInsert(ind, s) { 87 p = document.createElement("p"); 88 p.innerText = s; 89 myifbody.insertBefore(p, myifbody.getElementsByTagName("p")[ind]); 90 addOrRemoveSelect(true); 91 } 92 93 /** 94 * 将iframe.body的ind序号处的元素删除 95 * @param {number} ind 待删除的序号 96 */ 97 function doDelete(ind) { 98 myifbody.removeChild(myifbody.getElementsByTagName("p")[ind]); 99 addOrRemoveSelect(false); 100 } 101 102 /** 103 * 将一个内容为s的p标签添加到iframe.body尾部 104 * @param {string} s 待添加的字符串 105 */ 106 function doAppend(s) { 107 p = document.createElement("p"); 108 p.innerText = s; 109 myifbody.appendChild(p); 110 addOrRemoveSelect(true); 111 } 112 113 /** 114 * 从select下拉框中添加或移除一个元素 115 * @param {boolean} add true为添加,false为移除 116 */ 117 function addOrRemoveSelect(add) { 118 myselect = document.getElementById("myselect"); 119 if (add) { 120 toAppendOption = document.createElement("option"); 121 toAppendOption.setAttribute("value", myselect.length); 122 toAppendOption.innerText = myselect.length; 123 myselect.appendChild(toAppendOption); 124 } 125 else if (!add) { 126 myselect.removeChild(myselect.getElementsByTagName("option")[myselect.length - 1]); 127 } 128 }
caculatePoint.js
1 function sleep(numberMillis) { 2 var now = new Date(); 3 var exitTime = now.getTime() + numberMillis; 4 while (true) { 5 now = new Date(); 6 if (now.getTime() > exitTime) 7 return; 8 } 9 } 10 11 var cnt = 1; 12 function drawCurve(points, t) { 13 if (points.length == 1) { 14 // ctx.fillStyle = "#4F33DD"; 15 // document.write("<br/>" + "drawing: " + points[0].x + " " + points[0].y); 16 // ctx.fillRect(points[0].x, points[0].y, 1, 1); 17 var data = new Object(); 18 data.x = points[0].x; 19 data.y = points[0].y; 20 data.cnt = cnt; 21 this.postMessage(data); 22 sleep(1); 23 } 24 else { 25 newpoints = new Array(points.length - 1); 26 for (var i = 0; i < newpoints.length; i++) { 27 x = (1 - t) * points[i].x + t * points[i + 1].x; 28 y = (1 - t) * points[i].y + t * points[i + 1].y; 29 newpoints[i] = new Object(); 30 newpoints[i].x = x; 31 newpoints[i].y = y; 32 } 33 drawCurve(newpoints, t); 34 } 35 } 36 onmessage = function startHere(event) { 37 var width = 900, height = 750; 38 var n = 7; 39 var fir = new Object(), sec = new Object(); 40 fir.x = Math.random() * width; 41 fir.y = Math.random() * height; 42 sec.x = Math.random() * width; 43 sec.y = Math.random() * height; 44 while (true) { 45 var points = new Array(); 46 points[0] = fir; 47 points[1] = sec; 48 for (var i = 2; i < n; i++) { 49 var x = Math.random() * width; 50 var y = Math.random() * height; 51 points[i] = new Object(); 52 points[i].x = x; 53 points[i].y = y; 54 } 55 for (var t = 0.0001; t < 1; t += 0.0007) { 56 drawCurve(points, t); 57 } 58 cnt++; 59 fir.x = points[n - 1].x; fir.y = points[n - 1].y; 60 sec.x = points[n - 2].x; sec.y = points[n - 2].y; 61 } 62 }
main.css
1 @charset "utf-8"; 2 .my_iframe{ 3 box-shadow: lightgrey 1px 3px 5px; 4 border: none; 5 margin: 5px; 6 background:steelblue; 7 width: 1000px; 8 height: 450px; 9 } 10 .my_iframe_body{ 11 color: white; 12 font-family: Ubuntu Mono, '说说体'; 13 font-size: 23px; 14 } 15 .my_text_area{ 16 width: 995px; 17 height: 200px; 18 font-family: Ubuntu Mono, '说说体'; 19 display: block; 20 box-shadow: gray 1px 3px 5px; 21 font-size: 23px; 22 margin: 5px; 23 } 24 .my_bot_button{ 25 font-family: 说说体; 26 font-size: 19px; 27 float: left; 28 background: white; 29 padding: 10px; 30 margin-left: 5px; 31 display: block; 32 box-shadow: gray 1px 3px 5px; 33 } 34 .my_right_canvas{ 35 display: block; 36 box-shadow: gray 1px 3px 5px; 37 float: right; 38 margin-top: 5px; 39 margin-right: 20px 40 }
JS执行模型,对象模型,BOM以及DOM内存图: