前段时间想深入了解一下canvas,就写了一个canvas库.这段时间在忙其他的怕之前写的东西忘记了,特意想在这上面记一下。我不会一下写完,会慢慢更新。
先看一段代码:
// 创建一个画布渲染器
var _DxCanvas = new DxRender('#test', {
width: 1500,
height:1500,
layers: [new CanvasLayer()]
});
// 绘制一个正方形框
var rect = new RectShape({
x: 200,
y: 200,
width: 500,
height: 500,
style: new Style({
lineWidth: 1,
strokeStyle: '#000'
})
})
_DxCanvas.add(rect);
var beziers = [.25, .1, .25, 1];
var getBezierFormaul = function () {
var w = rect.options.width, h = rect.options.height, x = rect.options.x, y = rect.options.y;
return [beziers[0] * w + x, (1 - beziers[1]) * h + y, beziers[2] * w + x, (1 - beziers[3]) * h + y, x+w, y];
}
var currentBezier = getBezierFormaul();
// 更新轨迹线
var bezierFormula = function () {
line.set('points[0]', getBezierFormaul())
}
showbeziers();
// 创建轨迹线
var line = new BezierShape({
x: rect.options.x,
y: rect.options.y+rect.options.width,
points: [
getBezierFormaul() // 获取轨迹线的绘制路径
],
style: new Style({
lineWidth: 3,
strokeStyle:'red'
})
});
// 创建贝塞尔线第一个控制点
var controlCircle = new CircleShape({
x: currentBezier[2],
y: currentBezier[3],
r: 10,
style: new Style({
fillStyle: 'red'
})
});
// 创建贝塞尔线第二个控制点
var controlCircle2 = new CircleShape({
x: currentBezier[0],
y: currentBezier[1],
r: 10,
style: new Style({
fillStyle: 'red'
})
});
// 跟随轨迹线运动圆点
var controlCircle3 = new CircleShape({
x: 100,
y: 600,
r: 10,
style: new Style({
fillStyle: 'green'
})
});
// 给图形绑定事件
function bindEvent(element,type)
{
var controlEvent = {
isDown: false,
left: 0,
top: 0,
x: null,
y: null,
prev: null
};
element.on('mousedown', function (e) {
controlEvent.isDown = true;
controlEvent.left = element.get('x');
controlEvent.top = element.get('y');
controlEvent.x = e.x;
controlEvent.y = e.y;
})
_DxCanvas.on('mousemove', function (e) {
if (!controlEvent.isDown) {
return;
}
var cx = e.x, cy = e.y;
var px = cx - controlEvent.x, py = cy - controlEvent.y;
var nX = controlEvent.left + px;
var nY = controlEvent.top + py;
var maxW = rect.options.width;
var maxH = rect.options.height;
var left=rect.options.x;
var top = rect.options.y;
var dx = element.get('x'), dy = element.get('y');
if (type == 1) {
element.set('x', nX);
beziers[2] = toFixed((nX - left) / maxW,2);
element.set('y', nY);
beziers[3] =toFixed( 1-(nY - top) / maxH,2);
} else {
element.set('x', nX);
beziers[0] = toFixed((nX - left) / maxW, 2);
element.set('y', nY);
beziers[1] = toFixed(1 - (nY - top) / maxH, 2);
}
bezierFormula();
showbeziers();
controlEvent.prev = { x: cx, y: cy };
})
_DxCanvas.on('mouseup', function (e) {
console.log('mouseup');
controlEvent.isDown = false;
})
}
// 第一个控制点绑定移动事件
bindEvent(controlCircle,1)
// 第二个控制点绑定移动事件
bindEvent(controlCircle2, 2)
// 添加图形,到图层中
_DxCanvas.add(line);
_DxCanvas.add(controlCircle);
_DxCanvas.add(controlCircle2);
// 点击开始,运动点按着轨迹线开始运动
document.getElementById('start').addEventListener('click', function () {
_DxCanvas.add(controlCircle3);
// 增加一个动画
_DxCanvas.animation.addAnimator(new Animator({
x: 100,
y: 600
}, {
onFrame: function (target, p) {
var beziers = getBezierFormaul();
var o = bezier([[100, 600], [beziers[0], beziers[1]], [beziers[2], beziers[3]], [beziers[4], beziers[5]]], p);
controlCircle3.set('x', o.x);
controlCircle3.set('y', o.y);
},
onDone:function()
{
controlCircle3.remove();// 动画结束后,移动运动圆点
}
}).when(3000, {
x: 500,
y: 0
}).start())
});
先讲一下
- Element 存储图形属性基类
- Style 设置图片样式
- Text 文本
- Geometry 所有图形基类,它会继承(Element,Transformable,Animatable)
- Animation ,Animatable 动画引擎,图形动画
- Transformable 图形变换(位移、旋转、缩放等)
- Matrix 矩阵算法
- layer 图层
- render 图层渲染器
先讲一下Element 类
Element.extend
它的作用:主要是定义图形应用属性,监听属性变化。比如:上面代码中图形可以通过.set()可以改变图形属性,从而刷新图形.
init方法:会把图形上面定义的默认属性,扩展到实例对象自身或者options上面.默认是扩展在options对面上面
set(path,value,slient) 方法:
注意path它不是key。这两个不同key是一个属性名,path是属性路径。path支持set('data.list[0].x',x)
slient沉默。当为true就不刷新图形,只是简单修改属性。
refresh(sync)方法: sync 是同步更新
这段代码主要的作用是:
that.__refersh的作用:
假如:在不同函数中,有很多地方在操作A图形的属性。例如A.set('x',x),A.set('y',y)。那么我不管它操作多少次,我只会通知渲染器更新一次
scheduler.nextTick的作用:
假如:我们canvas上面,有上百个图形,发生了属性变化,那么我们不可能每个图形变化了,就去让canvas渲染器去渲染一遍,那通过这个调度的机制,先收集当前所有发生变化的图形放到队列中,等到所有代码执行完,再去通知渲染器去渲染,这个就是类似vue的nextTick
// 保存多次调用刷新时,只触发一次
if (!that.__refresh) {
that.__refresh = true;// 刷新中
scheduler.nextTick(function () {
that.__refresh = false;// 还原
that.emit('change');
});
}
下面看着代码讲解:
var Element = Observable.extend({
defaultOptions: {},
isExtendOptions:true,
init:function(options)
{
Observable.fn.init.call(this);
if (this.isExtendOptions) {
this.options = extend(true,{}, this.defaultOptions);
} else {
extend(true,this, this.defaultOptions);
}
this.setOptions(options,true);
this.__refresh = false;
},
_resolvePath:function(obj,path)
{
var paths = path.match(PATH_REGEXP), key = path, i, len, current=obj, result = {
key: '',
current:null
};
if (paths && paths.length > 1) {
key = paths.pop();
len = paths.length;
i = -1;
if (key.charAt(0) == '[') {
key = key.match(PATH_KEY_REGEXP)[0]
}
while (++i < len && current) {
current = current[paths[i]];
}
}
result.key = key;
result.current = current;
return result;
},
setOptions:function(options,slient)
{
if (this.isExtendOptions ) {
extend(this.options, options);
} else {
extend(this, options);
}
if(!slient)
{
this.refresh();
}
},
onChange:function(name,handler)
{
if (isFunction(name)) {
handler = name;
name = null;
this.on('change', handler);
} else {
this.on('change:'+name, handler);
}
},
removeChange: function (name, handler)
{
if (isFunction(name)) {
handler = name;
name = null;
this.off('change', handler);
} else {
this.off('change:' + name, handler);
}
},
refresh:function(sycn)
{
var that = this;
if (sycn)
{
that.emit('change');
return;
}
// 保存多次调用刷新时,只触发一次
if (!that.__refresh) {
that.__refresh = true;// 刷新中
scheduler.nextTick(function () {
that.__refresh = false;// 还原
that.emit('change');
});
}
},
has: function (path)
{
var that = this, paths = this._resolvePath(that.isExtendOptions ? that.options : that, path), key = paths.key, current = paths.current;
if (!current) {
return false;
}
return current.hasOwnProperty(key);
},
_set: function (path, value,slient)
{
var that = this, paths = this._resolvePath(that.isExtendOptions?that.options:that, path), key = paths.key, current = paths.current, oldVal;
if (!current) {
return;
}
oldVal= current[key];
if(oldVal===value)
{
return;
}
current[key] = value;
if (!slient) {
that.emit('change:' + path, value,key,path);
}
},
set:function(path, value,slient)
{
this._set(path, value, slient);
if (!slient) {
this.refresh();
}
},
get: function (path)
{
var that = this, paths = this._resolvePath(that.isExtendOptions ? that.options : that, path), key = paths.key, current = paths.current;
if (!current) {
return;
}
return current[key];
}
})