HTML5 + Javascript + CSS
Vì vậy, tôi đã làm điều đó từ lâu (ngày sửa đổi cuối cùng của tệp là 21/9/2012). Vui vì tôi đã giữ nó. Thật không may, nó chỉ hỗ trợ 4 điểm kiểm soát ở trạng thái hiện tại, nhưng tôi đang làm việc với nó.
EDIT: Mặc dù UI chỉ hỗ trợ 4 điểm kiểm soát, chức năng cơ bản (animateConstruction
) không hỗ trợ số lượng điểm kiểm soát tùy ý. Mặc dù tôi sẽ không đề xuất làm điều đó trong hơn 10 vì mã RẤT không hiệu quả. (Tôi đã thử với 25 và phải hủy tab bằng Trình quản lý tác vụ) Nếu điều này được tính là một lần gửi hợp lệ, tôi không có kế hoạch sửa đổi mã.
LƯU Ý: Lúc đó tôi là một người có sở thích ngây thơ. Mã này sai ở rất nhiều cấp độ (bao gồm thiếu dấu chấm phẩy và sử dụng eval
).
Sử dụng
Lưu mã dưới dạng tệp .html và mở nó trong Google Chrome hoặc JSfiddle
Nếu bạn cần 4 điểm kiểm soát trở xuống, hãy nhập các tham số ở bên phải, sau đó chọn "Chế độ xây dựng" và nhấn "Animate" ở dưới cùng bên trái.
Nếu bạn cần nhiều điểm kiểm soát hơn, hãy gọi animateConstruction
hàm. Nó lấy một mảng tọa độ (mảng 2 mục) làm đối số. (ví dụ animateConstruction([[0,0],[500,0],[0,500]]
). Lưu ý rằng vùng vẽ là 500x500 và hệ tọa độ tuân theo phần tử canvas HTML (gốc ở phía trên bên trái, trục x trỏ phải, trục y chỉ xuống)
Đối với fiddle, tôi đã thêm một hộp văn bản ở dưới cùng bên trái. Nhập tọa độ được phân tách bằng dấu chấm phẩy (giá trị mặc định là một ví dụ) và nhấn Go.
Sự khác biệt trong phiên bản Fiddle
- Hộp văn bản
- Các bước hoạt hình mặc định giảm xuống 100
- Đường cong phụ bị tắt theo mặc định
Mã
<html>
<head>
<style>
span.h{
display: inline-block;
text-align: center;
text-decoration: underline;
font: bold 1em Arial;
}
input[type="color"]{
-webkit-appearance: button-bevel;
vertical-align: -7px;
width: 21px;
height: 27px;
}
input[type="color"][disabled]{background: #FFF}
td{position:relative; padding:1px; text-align:center}
table[class] td{text-align:left}
td.t{padding:1px 5px; width:46px;}
table input[type="checkbox"]{visibility:hidden}
tr:hover input[type="checkbox"]{visibility:visible}
</style>
<script type='text/javascript'>
function Bezier(c){
if(c.length==2) return function(t){return [c[0][0]+t*(c[1][0]-c[0][0]),c[0][1]+t*(c[1][1]-c[0][1])]}
else return function(t){return Bezier([Bezier(c.slice(0,-1))(t),Bezier(c.slice(1))(t)])(t)}
}
function Bezier2(f1,f2){
return function(t){return Bezier([f1(t),f2(t)])(t)}
}
//============================================
var c = null
var settings = {'guide':{'show':[true,true,true,true], 'color':['#EEEEEE','#00FF00','#0000FF','#FF00FF'], 'width':[10,1,1,1]}, 'curve':{'show':[true,true,true,true], 'color':['#EEEEEE','#00FF00','#0000FF','#FF00FF'], 'width':[10,3,3,3]}, 'main':{'show':true, 'color':'#FF0000', 'width':10}, 'sample': 100, 'steps':200, 'stepTime':10, 'mode':'Bezier', 'coords':[[0,500],[125,450],[125,0],[500,0]]}
var itv = 0
window.addEventListener('load',function(){
c = $('c').getContext('2d')
c.lineCap = 'round'
c.lineJoin = 'round'
draw(settings.coords,1)
},true)
function get(k,i){
var t = settings
if(k.constructor == Array) k.forEach(function(e){t = t[e]})
return t.length>i ? t[i] : t.slice(-1)[0]
}
function frame(coords){
c.strokeStyle = settings.curve.color[0]
c.lineWidth = settings.guide.width[0]
c.beginPath()
c.moveTo.apply(c,coords[0])
coords.slice(1).forEach(function(e){c.lineTo.apply(c,e)})
c.stroke()
}
function transf(c){
var t = []
c.forEach(function(e){t.push([e[0]+5,e[1]+5])})
return t
}
//============================================
function drawBezier(coords,t){
if(t===undefined) t = 1
coords = transf(coords)
c.clearRect(0,0,510,510)
frame(coords)
c.beginPath()
c.strokeStyle = settings.main.color
c.lineWidth = settings.main.width
c.moveTo.apply(c,coords[0])
for(var i=0;i<=t*settings.sample;i++) c.lineTo.apply(c,Bezier(coords)(i/settings.sample))
c.stroke()
}
function animateBezier(coords){
var s = settings.steps
var cur = ($('t').value==1 ? ($('t').value=$('T').innerHTML=(0).toFixed(3))*1 : $('t').value*s)+1
var b = drawBezier(coords,$('t').value*1)
itv = setInterval(function(){
$("T").innerHTML = ($("t").value = cur/s).toFixed(3)
drawBezier(coords,cur++/s,b)
if(cur>s) clearInterval(itv)
},settings.stepTime)
}
//============================================
function drawBezier2(coords,t){
if(t===undefined) t = 1
c.beginPath()
c.strokeStyle = get(['curve','color'],coords.length-1)
c.lineWidth = get(['curve','width'],coords.length-1)
c.moveTo.apply(c,coords[0])
for(var i=0;i<=t*100;i++) c.lineTo.apply(c,Bezier(coords)(i/100))
c.stroke()
}
function drawConstruction(coords,t,B){
coords = transf(coords)
if(t===undefined) t = 0.5
var b = B===undefined ? [[]] : B
coords.forEach(function(e){b[0].push(function(t){return e})})
c.clearRect(0,0,510,510)
frame(coords)
for(var i=1;i<coords.length;i++){
if(B===undefined) b.push([])
with(c){
for(var j=0;j<coords.length-i;j++){
if(B===undefined) b[i].push(Bezier2(b[i-1][j],b[i-1][j+1]))
if(i!=coords.length-1 && get(['curve','show'],i-1) || i==coords.length-1 && settings.main.show){
strokeStyle = i==coords.length-1?settings.main.color:get(['curve','color'],i-1)
lineWidth = i==coords.length-1?settings.main.width:get(['curve','width'],i-1)
beginPath()
moveTo.apply(c,b[i][j](0))
for(var k=0;k<=t*settings.sample;k++) lineTo.apply(c,b[i][j](k/settings.sample))
stroke()
}
if(i && i!=coords.length-1 && get(['guide','show'],i)){
strokeStyle = i==coords.length-1?settings.main.color:get(['guide','color'],i)
lineWidth = i==coords.length-1?settings.main.width:get(['guide','width'],i)
beginPath()
if(i!=coords.length-1) arc.apply(c,b[i][j](t).concat([settings.curve.width[0]/2,0,2*Math.PI]))
stroke()
}
}
if(i && i!=coords.length-1 && get(['guide','show'],i)){
beginPath()
moveTo.apply(c,b[i][0](t))
for(var j=1;j<coords.length-i;j++) lineTo.apply(c,b[i][j](t))
stroke()
}
}
}
return b
}
function animateConstruction(coords){
var s = settings.steps
var cur = ($('t').value==1 ? ($('t').value=$('T').innerHTML=(0).toFixed(3))*1 : $('t').value*s)+1
var b = drawConstruction(coords,$('t').value*1)
itv = setInterval(function(){
$("T").innerHTML = ($("t").value = cur/s).toFixed(3)
drawConstruction(coords,cur++/s,b)
if(cur>s) clearInterval(itv)
},settings.stepTime)
}
//============================================
function draw(coords,t){clearInterval(itv); return window['draw'+settings.mode](coords,t)}
function animate(coords){clearInterval(itv); return window['animate'+settings.mode](coords);}
//============================================
function $(id){return document.getElementById(id)}
function v(o,p){
for(var i in o){
var k = (p||[]).concat([i]).join('-')
var t
if((t = o[i].constructor) == Object || t == Array) v(o[i],[k])
else if(t = $(k)){
if(t.type=='checkbox') t.checked = o[i]
else if(t.type=='radio'){
for(var j=0, t=document.getElementsByName(t.name); j<t.length; j++) if(t[j].value == o[i]){
t[j].checked = true
break
}
}else t.value = o[i]
}else if(t = $((i==0?'x':'y') + p[0].slice(-1))) t.value = o[i]
}
}
document.addEventListener('load',function(){
v(settings)
$('t').setAttribute('step',1/settings.steps)
var t = document.getElementsByTagName('input')
for(i=0;i<t.length;i++) t[i].addEventListener('change',function(){
var t
if((t=this.id.split('-')).length > 1){
var t1 = function(T){
var t = 'settings'
T.forEach(function(e){t += '[' + (isNaN(e)?'"'+e+'"':e) +']'})
eval(t + '=' + (this.type=='text'?this.value:(this.type=='checkbox'?this.checked:'"'+this.value+'"')))
$(T.join('-')).value = this.value
}
t1.call(this,t)
if(t[0]=='curve' && t[1]=='color' && $('u').checked==true) t1.call(this,['guide'].concat(t.slice(1)))
}else if(this.id == 'u'){
for(i=0;t=$('guide-color-'+i);i++){
t.disabled = this.checked
t.value = settings.guide.color[i] = this.checked?settings.curve.color[i]:t.value
}
}else if(this.id == 't'){
$('T').innerHTML = (this.value*1).toFixed(3)
draw(settings.coords,this.value*1)
}else if(t = /([xy])(\d+)/.exec(this.id)) settings.coords[t[2]*1][t[1]=='x'?0:1] = this.value*1
else settings[this.id] = this.value
if(this.id == 'steps') $("t").setAttribute("step",1/settings.steps)
},true)
},true)
</script>
</head>
<body>
<canvas style='float:left' width='510' height='510' id='c'>
</canvas>
<div style='padding-left:550px; font-family:Arial'>
<span class='h' style='width:123px'>Control Points</span><br />
(<input type='text' id='x0' size='3' maxlength='3' />,<input type='text' id='y0' size='3' maxlength='3' />)<br />
(<input type='text' id='x1' size='3' maxlength='3' />,<input type='text' id='y1' size='3' maxlength='3' />)<br />
(<input type='text' id='x2' size='3' maxlength='3' />,<input type='text' id='y2' size='3' maxlength='3' />)<br />
(<input type='text' id='x3' size='3' maxlength='3' />,<input type='text' id='y3' size='3' maxlength='3' />)<br /><br />
<span class='h' style='width:200px'>Appearance</span><br />
<span style='font-weight:bold'>Guide lines</span><br />
<input type='checkbox' checked='checked' id='u' onchange='' /> Use curve colors<br />
<table style='border-collapse:collapse'>
<tr><td><input type='checkbox' id='guide-show-0' /></td><td><input type='color' id='guide-color-0' disabled='disabled' /></td><td class='t'>Frame</td><td><input type='text' id='guide-width-0' size='2' maxlength='2' /></td></tr>
<tr><td><input type='checkbox' id='guide-show-1' /></td><td><input type='color' id='guide-color-1' disabled='disabled' /></td><td class='t'>1</td><td><input type='text' id='guide-width-1' size='2' maxlength='2' /></td></tr>
<tr><td><input type='checkbox' id='guide-show-2' /></td><td><input type='color' id='guide-color-2' disabled='disabled' /></td><td class='t'>2</td><td><input type='text' id='guide-width-2' size='2' maxlength='2' /></td></tr>
<tr><td><input type='checkbox' id='guide-show-3' /></td><td><input type='color' id='guide-color-3' disabled='disabled' /></td><td class='t'>3</td><td><input type='text' id='guide-width-3' size='2' maxlength='2' /></td></tr>
</table>
<span style='font-weight:bold'>Curves</span>
<table style='border-collapse:collapse'>
<tr><td><input type='checkbox' id='curve-show-0' /></td><td><input type='color' id='curve-color-0' /></td><td class='t'>1</td><td><input type='text' id='curve-width-0' size='2' maxlength='2' /></td></td></tr>
<tr><td><input type='checkbox' id='curve-show-1' /></td><td><input type='color' id='curve-color-1' /></td><td class='t'>2</td><td><input type='text' id='curve-width-1' size='2' maxlength='2' /></td></td></tr>
<tr><td><input type='checkbox' id='curve-show-2' /></td><td><input type='color' id='curve-color-2' /></td><td class='t'>3</td><td><input type='text' id='curve-width-2' size='2' maxlength='2' /></td></td></tr>
<tr><td><input type='checkbox' id='curve-show-3' /></td><td><input type='color' id='curve-color-3' /></td><td class='t'>4</td><td><input type='text' id='curve-width-3' size='2' maxlength='2' /></td></td></tr>
<tr><td><input type='checkbox' id='main-show' /></td><td><input type='color' id='main-color' /></td><td class='t'>Main</td><td><input type='text' id='main-width' size='2' maxlength='2' /></td></td></tr>
</table><br />
<span class='h' style='width:300px'>Graphing & Animation</span><br />
<table class>
<tr><td>Sample points:</td><td><input type='text' id='sample' /></td></tr>
<tr><td>Animation steps:</td><td><input type='text' id='steps' /></td></tr>
<tr><td>Step time:</td><td><input type='text' id='stepTime' />ms</td></tr>
</table>
<div style='position:absolute; top:526px; left:8px; width:510px; height:100px;'>
<input type='range' id='t' max='1' min='0' style='width:450px' value='1' /> <span id='T' style='vertical-align: 6px'>1.000</span><br />
<input type='button' onclick='draw(settings.coords,$("t").value*1)' value='Draw' /><input type='button' onclick='animate(settings.coords)' value='Animate' />
<input type='radio' id='mode' name='mode' value='Bezier' />Basic Mode <input type='radio' id='mode' name='mode' value='Construction' />Construction Mode
</div>
</body>
</html>