Get Started.md 23 KB

Kity Graphic : Get Started

本文档简单介绍 Kity Graphic 中的概念和使用

基础框架

简单介绍 Kity Graphic 的设计。

Class 系统

Kity Graphic 使用 OOP 的编程和接口风格,通过创建对象和在对象上进行操作来实现图形的绘制、移动、更新、动画、交互等功能。

用户可以通过继承现有的类来封装更多所需要的图形,用以复用。

关于 Kity Class 系统的更详细文档,请参考 Kity Graphic OOP.md (还没写,别着急点)

Pipe 函数

所有 Kity Graphic 的对象都有一个 pipe 函数,它做的事情很简单:接收一个函数 fn,这个函数会被调用,并且调用的上下文以及第一个参数都是当前对象。这个函数可以让对对象的一连串操作整理的干净起来。

下面是不使用 pipe 函数绘制一个用渐变填充、虚线描边并且旋转了30度的矩形的代码。

var paper = new Paper(document.body);
var rect = new Rect(0, 0, 10, 10);
var brush = new LinearGradientBrush();

brush.addStop(0, new Color('red'));
brush.addStop(0.5, new Color('yellow'));
brush.addStop(1, new Color('blue'));
paper.addResource( this );
rect.fill( brush );

var pen = new Pen();
pen.setDashArray([2, 2]);
pen.setWidth(1);
pen.setColor('gray');
rect.stroke( pen );

rect.rotate(30);

这个代码看起来还行,不过对比一下使用 pipe 函数的版本:

var paper = new Paper(document.body);

paper.addShape(new Rect(0, 0, 10, 10).pipe( function() {

    this.fill(new LinearGradientBrush.pipe( function( brush ) {
        brush.addStop(0, new Color('red'));
        brush.addStop(0.5, new Color('blue'));
        brush.addStop(1, new Color('yellow'));
        paper.addResource(brush);
    }));
    
    this.stroke(new Pen().pipe( function( pen ) {
        pen.setDashArray([2, 2]);
        pen.setWidth(1);
        pen.setColor('gray');
    }));
    
    this.rotate(30);
    
}));

恰当地使用 pipe 函数,可以提高代码的可读性和美观性。

使用 Paper

Paper 是所有图形和资源的跟容器,所有图形和资源(资源的概念请参照“Resources”)由 Paper 集中管理。

初始化 Paper

Kity 中所有的对象都是通过使用 new 运算符创建的,有一些类型要求你在创建的时候传递必须的参数到构造函数中。用下面的代码可以创建一个 Paper,并且会在指定的容器中渲染:

var paper = new Paper('container');

// 或者直接传 Dom 对象:
var paper = new Paper(document.body);

如果需要,你也可以重新获取容器:

var container = paper.getContainer();

设置宽高和视野

宽高和视野是对 Paper 最基本的设置。

宽高指的是 Paper 在浏览器中渲染的大小,可以使用像素或百分比作为单位:

paper.setWidth(800).setHeight(600);
// 或使用百分比:
paper.setWidth('100%').setHeight('100%');

视野定义了 Paper 下图形的坐标系统。由四个值来定义:( x, y, width, height )。其中 x 和 y 确定 Paper 左上角的点再坐标系里的坐标,而 width 和 height 就表示 Paper 显示的坐标范围。

Viewbox 说明

上面两个矩形的大小都是 60 * 40,左上角坐标都是 (10, 10),但是因为 Paper 的 ViewBox 不一样,导致了其呈现不一样。设置 Paper 的 ViewBox 使用 setViewBox 接口:

paper.setViewBox(0, 0, 400, 300);

如果需要,也可以获得 Paper 当前的 ViewBox:

/*
    vbox is like: 
    {
        x: 0,
        y: 0,
        width: 400,
        height: 300
    }
*/
var vbox = paper.getViewBox();

图形管理

Paper 是一个图形容器(ShapeContainer),可以向其添加和移除图形:

// 添加单个图形
paper.addShape( new Rect(0, 0, 10, 10) );

// 添加多个图形
paper.addShapes( [
    new Circle(100, 100, 10),
    new Circle(200, 200, 10)
] );

// 通过以下方式移除已经添加的图形:
paper.removeShape( rect );

// 或者更简单的:
rect.remove();

要获得当前 Paper 上所有的图形,可以使用 paper.getShapes() 接口。

paper.clear(); // clear all items
paper.addShape( rect );
paper.addShape( circle );

所有的图形在创建的时候就会自动生成一个唯一的 id,用户也可以去使用自己设置的 id。 假如自己设置了图形的 id,那么可以根据 id 获得图形:

rect.setId('my-rect');
paper.addShape(rect);
assert( paper.getShapeById('my-rect') === rect ) // true

ShapeContainer 在 Kity 中都有统一的接口:

.addShape()
.addShapes()
.getShapes()
.removeShape()
.getShapeById()
.clear()

被添加到容器中的元素,会有一个 container 字段指向其容器的引用,比如添加到 Paper 中的图形:

paper.addShape( rect );
assert( rect.container === paper ); //true

rect.remove();
assert( rect.container === paper ); //false
assert( rect.remove === undefined ); //true

资源管理

在使用 Kity Graphic 的过程中,有一些需要使用的资源,需要加到 Paper 上,才会产生效果。比如 LinearGradientBrushRadialGradientBrushPatternBrush 等。往 Paper 添加和移除资源使用以下接口:

var brush = new LinearGradientBrush().pipe(function() {
    this.addStop(0, new Color('red'));
    this.addStop(1, new Color('blue'));
});
paper.addResource( brush );
rect.fill( brush );

// 资源被移除后,矩形的填充会失效
paper.removeResource( brush );

创建图形

Kity Graphic 内置了 PathRectEllipseCirclePolylinePolygonCurveBezier 等基本几何图形。

Path

Path 是 Kity 中最强大的工具,可以绘制任意图形。其他的几何图形都是继承 Path 而来。Path 能识别 SVG 中定义的 Path Data 字符串格式。可以通过这样一个字符串构造 Path:

var triangle = new Path('M 0 0 L 100 100 100 200 Z');

也可以直接访问这个 Path Data:

triangle.setPathData('M 0 0 L 100 100 0 100 Z');
console.log(triangle.getPathData());

当然,如果不喜欢拼凑字符串,或者怕拼凑出错的用户,可以选择使用 PathDrawer 来绘制路径:

var triangle2 = new Path();
var d = triangle2.getDrawer();
d.pipe(function() {
    this.moveTo(0, 0);
    this.lineTo(100, 100);
    this.lineTo(50, 173);
    this.close();
});
console.log(triangle2.getPathData()); // 'M 0 0 L 100 100 L 50 173 Z'

path.getDrawer() 返回的是一个 PathDrawer 实例,它与具体的 Path 绑定。PathDrawer 的方法包括:

d.moveTo(x, y)
d.moveBy(dx, dy)
d.lineTo(x, y)
d.lineBy(dx, dy)
d.arcTo(rx, ry, xr, laf, sf, x, y )
d.arcBy(rx, ry, xr, laf, sf, dx, dy )
d.carcTo(r, x, y, laf, sf)
d.carcBy(r, dx, dy, laf, sf)
d.bezierTo(x1, y1, x2, y2, x, y)
d.bezierBy(dx1, dy1, dx2, dy2, dx, dy)
d.close()
d.clear()

其中 carcToarcTo 的快捷操作,用于绘制圆弧

Rect

Rect 是使用非常广泛的图形。Rect 的参数非常简单:

var rect = new Rect(10, 20, 100, 200);
console.log( rect.getX() ); // 10
console.log( rect.getY() ); // 20
console.log( rect.getWidth() ); // 100
console.log( rect.getHeight() ); // 200

你可以随时更改矩形的位置和宽高:

rect.setX(20).setY(10);
rect.setWidth(100).setHeight(200);
// or
rest.setPosition(20, 10)
rect.setSize(100, 200);

Kity 的矩形支持圆角:

var rect = new Rect(10, 20, 100, 200, 5);
console.log( rect.getRadius() ); // 5

圆角也可以随时修改

Ellipse

Ellipse 用于绘制一个椭圆:

// 圆心:(0, 0)
// x 轴半径:200
// y 轴半径:100
var ellipse = new Ellipse(0, 0, 200, 150);
console.log( ellipse.getCenter() ); // {x: 0, y: 0}
console.log( ellipse.getRadiusX() ); // 200
console.log( ellipse.getRadiusY() ); // 150

可以随时更改椭圆圆心位置以及半径:

ellipse.setCenterX(100).setCenterY(200);
ellipse.setRadiusX(30).setRadiusY(40);  
// or
ellipse.setCenter(100, 200);
ellipse.setRadius(30, 40);

Circle

Circle 用于绘制一个圆形:

// 圆心:(200, 300)
// 半径:50
var circle = new Circle(200, 300, 50);
console.log( circle.getCenter() ); // {x: 200, y: 300}
console.log( circle.getRadius() ); // 50 

可以随时更改圆形的圆心及半径

// 修改圆心
circle.setCenter(100, 200);
// or
circle.setCenterX(100).setCenterY(200);

// 修改半径
circle.setRadius(60);

Polyline

Polyline 用于绘制折线,通过添加顶点到折线上,可以形成经过这些点的折线

var polyline = new Polyline().pipe(function() {
    this.addPoint( new ShapePoint(10, 10) );
    this.addPoint( new ShapePoint(22, 33) );
    this.addPoint( new ShapePoint(32, 12) );
});

// 偷懒写法:
var polyline = new Polyline().pipe(function() {
    this.addPoint(10, 10);
    this.addPoint(22, 33);
    this.addPoint(32, 12);
});

也可以在创建时直接指定顶点:

var polyline = new Polyline([
    new ShapePoint(10, 10),
    new ShapePoint(22, 33),
    new ShapePoint(32, 12)
]);

对顶点的改变是可以直接影响图形外观的:

// 让折线第二个顶点的位置设置为(10, 20)
polyline.getPoint(1).setX(10).setY(20);

对于折线中顶点的处理提供了强大的功能:

// clear 方法可以清除点集
polyline.clear();

// 可以批量添加点集
polyline.addPoints([
    new ShapePoint(0, 0),
    new ShapePoint(10, 10),
    new ShapePoint(20, 20)
]);

// 获得第1个点
polyline.getPoint(0);
polyline.getFirstPoint();

// 获得第2个点
polyline.getPoint(1);

// 获得最后一个点
polyline.getLastPoint();

// 插入指定的点到第2个点的位置
polyline.addPoint(new ShapePoint(20, 20), 1);

Polygon

Polygon 用于绘制多边形,其使用方法和 Polyline 完全一致,只是最后会在首位两个点连一条直线,闭合成多边形。

Curve

Curve 是聪明的曲线,该曲线经过用户指定的点集,并且智能地使用曲线连接。如果曲线闭合,则形成一个平滑的包围形状。

var curve = new Curve().pipe(function() {
    this.addPoint( new ShapePoint(10, 10) );
    this.addPoint( new ShapePoint(24, 33) );
    this.addPoint( new ShapePoint(63, 22) );
    this.setSmoothFactor( 2 );
});

setSmoothFactor() 设置转折点处的平滑程度,值越大越平滑,为 0 的时候绘制成折线,默认值为 1。

Bezier

Bezier 用于绘制贝塞尔曲线。贝塞尔曲线由一系列的转换点构成,每个转换点包含一个顶点坐标以及两个控制点坐标。其中顶点坐标是绝对坐标,控制点坐标是相对顶点的坐标。如果转换点是设置为平滑的,那么两个控制点是会相互影响的,否则将相对独立。

var bezier = new Bezier().addPoints([
    new BezierPoint(300, 150, false).setForward(350, 50).setBackward(250, 50),
    new BezierPoint(450, 150).setForward(470, 200),
    new BezierPoint(300, 350, false).setForward(200, 300).setBackward(400, 300),
    new BezierPoint(150, 150).setBackward(130, 200),
    new BezierPoint(300, 150, false).setForward(350, 50).setBackward(250, 50)
]);

贝塞尔曲线

贝塞尔曲线是贝塞尔转换点(BezierPoint)的集合,BezierPoint 本身关注四个属性:顶点位置、前向控制点位置、背向控制点位置、是否平滑。

var p1 = bezier.getPoint(0);
var p2 = bezier.getPoint(1);
var p3 = bezier.getPoint(2);

// 重新设置 p2 顶点的位置
p2.setVertex(400, 400)

// 设置前向控制点会影响到下一段曲线的形状(p2 -> p3),
// 如果 p2 是平滑的,则会同时影响上一段曲线的形状(p1 -> p2)
p2.setFoward(500, 500);

// 设置背向控制点会影响到上一段曲线的形状(p1 -> p2)
// 如果 p2 是平滑的,则会同时影响下一段曲线的形状(p2 -> p3)
p2.setBackward(300, -300);

// 如果 p2 是平滑的(默认),那么对前向控制点和背向控制点的改变将会
// 影响对方,以保证两个控制点和顶点是在一条直线上的,如果设置为 false
// 则两个控制点可以相互独立
p2.setSmooth(true);

使用 Group 来建立图形分组

对图形分组可以把这些图形进行一个整体的设置:

var flower = new Group().pipe(function() { 
    for(var i = 0; i < 12; i++) {
        var color = new Color();
        color.set('l', 50);
        color.set('s', 80);
        color.set('h', 30 * i);
        color.set('a', .5);
        this.addShape(new Ellipse(50, 0, 80, 15).rotate( 30 * i ).fill(color));
    }
    this.scale(1.5).translate(300, 200);
});

使用 Group 来绘制花

组本身也是一个图形(由其子元素组合),所以也可以被添加到组里。

var group = new Group();
group.addShape( flower );

填充图形

默认添加到 Paper 上的图形是不具有视觉呈现的,需要对其进行填充或描边。

纯色填充 - 使用 Color

要用一个颜色进行填充,可以:

rect.fill( new Color('red') );
// 或者直接使用字符串:
rect.fill( 'red' );

使用 LinearGradientBrush 进行线性渐变填充

线性渐变使用 LinearGradientBrush 进行填充:

rect.fill(new LinearGradientBrush().pipe( function() {
    this.addStop(0, 'red');
    this.addStop(1, 'blue');
    this.setStartPosition(0, 0);
    this.setEndPosition(1, 1);
    paper.addResource(this);
}));

线性渐变

setStartPosition()setEndPosition 决定了填充的方向和范围。其中 (0,0) 代表图形的左上角,(1, 1) 代表图形的右下角

addStop()添加关键颜色到具体位置,其中 0 表示渐变开始的位置,1 表示渐变结束的位置

NOTE: LinearGradientBrush 属于资源,需要添加到 Paper 上才能生效

使用 RadialGradientBrush 进行径向渐变填充

进项渐变使用 RadialGradientBrush 进行填充:

rect.fill(new RadialGradientBrush().pipe( function() {
    this.setCenter(0.5, 0.5);
    this.setRadius(0.8);
    this.setFocal(0.8, 0.2);
    this.addStop(0, 'white');
    this.addStop(1, 'gray');
    paper.addResource(this);
}));

径向渐变

addStop() 的概念用法和用法与 LinearGradientBrush 是一致的。

颜色会从中心点(Center)向边缘扩散。中心点可以通过setCenter() 方法设置,默认是 (0.5, 0.5) 。

setRadius() 方法设置渐变的半径,默认是 0.5,正好到达图形边缘(最近边缘)。可以设置为大于 0.5。

NOTE: RadialGradientBrush 属于资源,需要添加到 Paper 上才能生效

使用 PatternBrush 进行图形填充

PatternBrush 是最灵活的画笔,它可以用图形填充图形。

rect.fill(new PatternBrush().pipe( function() {
    var colors = ['red', 'blue', 'yelow', 'green'];
    this.setWidth(40);
    this.setHeight(40);
    this.addItem( new Circle(10, 10, 5).fill(colors.shift()) );
    this.addItem( new Circle(30, 10, 5).fill(colors.shift()) );
    this.addItem( new Circle(10, 30, 5).fill(colors.shift()) );
    this.addItem( new Circle(30, 30, 5).fill(colors.shift()) );
    paper.addResource(this);
}));

图形填充

addItem() 方法向 PatternBrush 添加任意的图形。事实上,PatternBrush 是一个图形的 Container,具有 Container 的方法。

setHeight()setWidth() 方法设置每一个填充块的大小

NOTE: PatternBrush 属于资源,需要添加到 Paper 上才能生效

描边图形

Pen 用于描绘图形的轮廓:

path.stroke(new Pen().pipe(function() {
    this.setWidth(5);
    this.setDashArray([10, 5]);
    this.setLineCap('butt');
    this.setLineJoin('round');
    this.setColor('green');
}));

描边

setWidth() 设置画笔的粗细,默认为 1

setDasArray() 设置画笔的段长和间隙长,不断循环。默认为 null,绘制实线

setLineCap() 设置端点的样式,取值有:butt、round、suqare

setLineJoin() 设置转折点的样式,取值有:miter、round、bevel

关于 line-join 和 line-cap 的效果可以参考SVG标准

文字

可以添加文字到 Paper 上:

paper.addShape(new Text("hello, kity").setX(50).setY(50));

使用文本块控制样式

可以通过添加不同的 TextSpan 控制样式

paper.addShape(new Text().pipe(function() {
    this.addSpan(new TextSpan('hello ').fill('red'));
    this.addSpan(new TextSpan(' kity').fill('blue'));
    this.setX(300);
    this.setY(200);
    this.setAnchor('middle');
    this.setSize(36);
}));

TextSpan 使用

使用文本路径排列文字

给 Text 指定一个 Path,可以让文本按照 Path 排列

text.setPath(new Path().pipe(function(){
    var d = this.getDrawer();
    d.moveTo(150, 200);
    d.carcTo(200, 450, 200);
    this.stroke('green');
    paper.addShape(this);
}));

TextPath 的使用

颜色

支持的格式

Kity 的颜色支持 RGBA / HSLA 两种。RGBA 使用 RGB 三个分量(红、绿、蓝)来决定颜色值,用 A 分量表示透明度。HSLA 使用 HSL 三个分量(色相、饱和度、亮度)来决定颜色值。RGBA 更符合设备表示颜色的习惯,而 HSLA 更符合人类表示颜色的习惯。

对于 RGBA 还支持 HEX 的表示方式,不过该表示方式会丢失透明度信息。

使用颜色

Color 对象有好多种创建方式


// 创建 RGB 颜色
c2 = Color.createRGB(255, 0, 0);
c3 = Color.createRGBA(255, 0, 0, 0.5);

// 创建 HSL 颜色
c4 = Color.createHSL(180, 50, 80);
c5 = Color.createHSLA(180, 50, 80, 0.6);

// 使用能识别的颜色字符串创建
c6 = Color.parse('#ff0000');
c7 = Color.parse('#666');
c8 = Color.parse('rgb(255,255,0)');
c9 = Color.parse('rgba(255, 128, 0, .4)');
c10 = Color.parse('hsl(240, 80%, 50%)');
c11 = Color.parse('hsla(240, 80, 50, .5)'); // % 可以省略

// 使用熟知颜色名创建
c12 = Color.parse('blue');
c13 = Color.parse('green');

颜色分量

对于 RGBA、HSLA 的分量是可以独立设置的。

// 设置 RGB 分量
var red = new Color();
red.set(Color.R, 255);
red.set(Color.G, 0);
red.set(Color.B, 0);

// 设置 HSL 分量
var blue = new Color();
blue.set(Color.H, 240);
blue.set(Color.S, 100);
blue.set(Color.L, 60);

// 设置 A 分量
blue.set(Color.A, 0.5);

你还可以通过改变某个分量的值,获得一个新的颜色。

var red = Color.parse('red');
var lightred = red.inc(Color.L, 20);
var darkred = red.dec(Color.L, 20);

使用调色板

调色板提供一个使用颜色的思路,Kity 不提倡直接把颜色值写在代码里,而是使用调色板定义颜色值,然后在需要的地方引用。

var myPalette = new Palette();
myPalette.add('niceblue', Color.createHSL(240, 30, 60));

rect.fill(myPalette.get('niceblue'));

使用 CSS 样式表

可以把图形加入 CSS 的 ClassName,通过样式表来控制图形样式。

// .blue {
//     fill: 'blue';
// }
rect.addClass('blue');

也可以直接设置样式

// 效果和 rect.fill('blue') 是一样的
rect.setStyle('fill', 'blue');
rect.setStyle({
    'stroke': 'red',
    'stroke-width': 1
});

变换矩阵

可以在所有的图形上应用变换矩阵,实现图形的平移、旋转、缩放、拉伸等。

平移图形

对于任意图形,使用 translate 方法可以产生平移效果:

var rect = new Rect(0, 0, 200, 100);
rect.translate(30, 30);

平移

旋转图形

使用 rotate 方法,可以让图形进行旋转

var rect = new Rect(0, 0, 200, 100);
rect.rotate(30);

旋转 30 度

上面这个旋转是以坐标原点(0, 0) 为锚点的,如果想要以指定的锚点进行旋转,可以指定锚点的坐标:

var rect = new Rect(0, 0, 200, 100);
rect.rotate(30, 100, 50);

指定锚点进行旋转

缩放图形

使用 scale 方法对图形进行缩放:

var rect = new Rect(0, 0, 100, 100);
rect.scale(1.5);

等比缩放

你也可以在 x 轴和 y 轴上进行不同比例的缩放:

var rect = new Rect(0, 0, 100, 100);
rect.scale(1.5, 0.5);

不等比缩放

默认缩放也是以坐标原点为锚点的,可以指定锚点进行缩放:

var rect = new Rect(0, 0, 100, 100);
rect.scale(1.5, 50, 50);

指定锚点缩放

拉伸倾斜

使用 skew 可以对图形进行倾斜。

var rect = new Rect(0, 0, 100, 100);
rect.skew(30);

两个方向倾斜

你可以单独在 x 方向 或 y 方向倾斜:

var rect = new Rect(0, 0, 100, 100);
rect.skew(30, 0); // x 方向倾斜
rect.skew(0, 30); // y 方向倾斜

x 方向倾斜 y 方向倾斜

其他变换

如果会使用 matrix,可以使用更灵活的变换。matrix 的 6 个参数 a, b, c, d, e, f 在变换的时候作为参数,变换是线性的:

X' = aX + cY + e
Y' = bX + dY + f

比如要对图形以直线 y = x 为轴进行镜像,那么转换的时候应该是:

X' = Y
Y' = X

对应的参数应该是 a = 0, b = 1, c = 1, d = 0, e = 0, f = 0。

var triangle = new Path('M0 0 L 150 0 0 50 Z');
triangle.setTransform(new Matrix(0, 1, 1, 0, 0, 0));

自定义变换

BoundaryBox

BoundaryBox 是图形的最小包含矩形。可以理解为图形的边界。

使用 getBoundaryBox() 方法获取图形的包围矩形。

var rect = new Rect(0, 0, 200, 300);

console.log( rect.getBoundaryBox() ); 
// { x: 0, y: 0, width: 200, height: 300 }

RenderBox

图形经过所有的变换之后,并不影响其原来的 BoundaryBox。经过变换之后的图形,需要使用 getRenderBox() 方法来获取图形的真正边界。

var rect = new Rect(0, 0, 200, 300);

console.log( rect.getBoundaryBox() ); 
// { x: 0, y: 0, width: 200, height: 300 }

console.log( rect.getRenderBox() );
// { x: 0, y: 0, width: 200, height: 300 }

rect.translate(100, 100);

console.log( rect.getBoundaryBox() );
// { x: 0, y: 0, width: 200, height: 300 }

console.log( rect.getRenderBox() );
// { x: 100, y: 100, width: 200, height: 300 }

事件

Kity 的对象支持事件绑定。

绑定到图形上

rect.on('click', function(e) {
    // e.targetShape === rect
    alert('you click my rect!!');
});

绑定到 Paper 上

paper.on('click', function(e) {
    alert('you click ' + e.targetShape.getId());
});

使用坐标

使用 e.getPosition() 获得鼠标位置在用户坐标系统中的坐标

paper.on('click', function(e) {
    var p = e.getPosition();
    var circle = new Circle( p.x, p.y, 3, 3 );
    paper.addShape(circle);
});

文档结束