├── tools
├── demo
│ ├── app.wxss
│ ├── package.json
│ ├── app.js
│ ├── pages
│ │ └── index
│ │ │ ├── index.json
│ │ │ ├── index.wxss
│ │ │ ├── index.wxml
│ │ │ └── index.js
│ ├── app.json
│ └── project.config.json
├── config.js
├── checkwxss.js
├── checkcomponents.js
├── utils.js
└── build.js
├── src
├── index.wxss
├── assets
│ ├── copy.wxss
│ └── icon.png
├── index.json
├── index.wxml
├── shapes
│ ├── triangle.class.js
│ ├── circle.class.js
│ ├── ellipse.class.js
│ ├── rect.class.js
│ ├── polygon.class.js
│ ├── image.class.js
│ ├── object.class.js
│ └── text.class.js
├── utils
│ ├── object.js
│ ├── string.js
│ ├── index.js
│ └── misc.js
├── index.js
├── mixins
│ ├── shared_methods.mixin.js
│ ├── observable.mixin.js
│ ├── text_style.mixin.js
│ ├── object_origin.mixin.js
│ ├── object_interactivity.mixin.js
│ ├── canvas_events.mixin.js
│ └── object_geometry.mixin.js
├── sugarjs.js
├── pattern.class.js
├── intersection.class.js
├── gradient.class.js
├── point.class.js
├── color.class.js
└── canvas.class.js
├── .gitignore
├── .npmignore
├── .babelrc
├── test
├── wx.test.js
├── index.test.js
└── utils.js
├── tsconfig.json
├── gulpfile.js
├── LICENSE
├── publish.js
├── package.json
├── .eslintrc.js
└── README.md
/tools/demo/app.wxss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tools/demo/package.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/tools/demo/app.js:
--------------------------------------------------------------------------------
1 | App({})
2 |
--------------------------------------------------------------------------------
/src/index.wxss:
--------------------------------------------------------------------------------
1 | .index {
2 | color: green;
3 | }
4 |
--------------------------------------------------------------------------------
/src/assets/copy.wxss:
--------------------------------------------------------------------------------
1 | page {
2 | width: 100%;
3 | }
4 |
--------------------------------------------------------------------------------
/src/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
5 |
--------------------------------------------------------------------------------
/src/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suguoyao/miniprogram-canvas-sugarjs/HEAD/src/assets/icon.png
--------------------------------------------------------------------------------
/tools/demo/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "sugar": "../../components/index"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | package-lock.json
4 |
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 |
11 | miniprogram_dist
12 | miniprogram_dev
13 | node_modules
14 | coverage
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | package-lock.json
4 |
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 |
11 | test
12 | tools
13 | docs
14 | miniprogram_dev
15 | node_modules
16 | coverage
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | ["module-resolver", {
4 | "root": ["./src"],
5 | "alias": {}
6 | }]
7 | ],
8 | "presets": [
9 | ["env", {"loose": true, "modules": "commonjs"}]
10 | ]
11 | }
--------------------------------------------------------------------------------
/tools/demo/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages":[
3 | "pages/index/index"
4 | ],
5 | "window":{
6 | "backgroundTextStyle":"light",
7 | "navigationBarBackgroundColor": "#fff",
8 | "navigationBarTitleText": "Canvas-SugarJS",
9 | "navigationBarTextStyle":"black"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/index.wxml:
--------------------------------------------------------------------------------
1 |
12 |
13 | Canvas-SugarJS
14 |
--------------------------------------------------------------------------------
/test/wx.test.js:
--------------------------------------------------------------------------------
1 | const _ = require('./utils')
2 |
3 | test('wx.getSystemInfo', async () => {
4 | wx.getSystemInfo({
5 | success(res) {
6 | expect(res.errMsg).toBe('getSystemInfo:ok')
7 | },
8 | complete(res) {
9 | expect(res.errMsg).toBe('getSystemInfo:ok')
10 | },
11 | })
12 | })
13 |
14 | test('wx.getSystemInfoSync', async () => {
15 | const info = wx.getSystemInfoSync()
16 | expect(info.SDKVersion).toBe('2.4.1')
17 | expect(info.version).toBe('6.6.3')
18 | })
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "esnext",
4 | "target": "es2015",
5 | "lib": ["es2015", "es2017", "dom"],
6 | "noImplicitAny": false,
7 | "moduleResolution": "node",
8 | "sourceMap": true,
9 | "baseUrl": ".",
10 | "allowSyntheticDefaultImports": true,
11 | "experimentalDecorators": true,
12 | "emitDecoratorMetadata":true,
13 | "esModuleInterop": true,
14 | "resolveJsonModule": true
15 | },
16 | "files": [
17 | "node_modules/miniprogram-api-typings/index.d.ts"
18 | ],
19 | "include": [
20 | "src/**/*.ts"
21 | ],
22 | "exclude": [
23 | "node_modules"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | const _ = require('./utils')
2 |
3 | test('render', async () => {
4 | const componentId = _.load('index', 'comp')
5 | const component = _.render(componentId, {prop: 'index.test.properties'})
6 |
7 | const parent = document.createElement('parent-wrapper')
8 | component.attach(parent)
9 |
10 | expect(_.match(component.dom, 'index.test.properties-falseother.properties-other')).toBe(true)
11 |
12 | await _.sleep(10)
13 |
14 | expect(_.match(component.dom, 'index.test.properties-trueother.properties-other')).toBe(true)
15 | })
16 |
--------------------------------------------------------------------------------
/src/shapes/triangle.class.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/6/6.
3 | */
4 | const ObjectClass = require('./object.class')
5 |
6 | class TriangleClass extends ObjectClass {
7 | constructor(options) {
8 | super()
9 |
10 | this.type = 'triangle'
11 | this.width = 50
12 | this.height = 50
13 |
14 | this.initialize(options)
15 | }
16 |
17 | initialize(options) {
18 | super.initialize(options)
19 | }
20 |
21 | _render(ctx) {
22 | let widthBy2 = this.width / 2
23 | let heightBy2 = this.height / 2
24 |
25 | ctx.beginPath()
26 | ctx.moveTo(-widthBy2, heightBy2)
27 | ctx.lineTo(0, -heightBy2)
28 | ctx.lineTo(widthBy2, heightBy2)
29 | ctx.closePath()
30 |
31 | this._renderPaintInOrder(ctx)
32 | }
33 | }
34 |
35 | module.exports = TriangleClass
36 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp')
2 | const clean = require('gulp-clean')
3 |
4 | const config = require('./tools/config')
5 | const BuildTask = require('./tools/build')
6 | const id = require('./package.json').name || 'miniprogram-custom-component'
7 |
8 | // 构建任务实例
9 | // eslint-disable-next-line no-new
10 | new BuildTask(id, config.entry)
11 |
12 | // 清空生成目录和文件
13 | gulp.task('clean', gulp.series(() => gulp.src(config.distPath, {read: false, allowEmpty: true}).pipe(clean()), done => {
14 | if (config.isDev) {
15 | return gulp.src(config.demoDist, {read: false, allowEmpty: true})
16 | .pipe(clean())
17 | }
18 |
19 | return done()
20 | }))
21 | // 监听文件变化并进行开发模式构建
22 | gulp.task('watch', gulp.series(`${id}-watch`))
23 | // 开发模式构建
24 | gulp.task('dev', gulp.series(`${id}-dev`))
25 | // 生产模式构建
26 | gulp.task('default', gulp.series(`${id}-default`))
27 |
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const simulate = require('miniprogram-simulate')
4 | const config = require('../tools/config')
5 |
6 | // const dir = config.srcPath // 使用源码进行测试,对于 debug 和代码覆盖率检测会比较友好
7 | const dir = config.distPath // 使用构建后代码进行测试,如果使用了 typescript 进行开发,必须选择此目录
8 |
9 | try {
10 | fs.accessSync(dir)
11 | } catch (err) {
12 | console.error('请先执行 npm run build 再进行单元测试!!!')
13 | }
14 |
15 | const oldLoad = simulate.load
16 | simulate.load = function (componentPath, ...args) {
17 | if (typeof componentPath === 'string') componentPath = path.join(dir, componentPath)
18 | return oldLoad(componentPath, ...args)
19 | }
20 |
21 | module.exports = simulate
22 |
23 | // adjust the simulated wx api
24 | const oldGetSystemInfoSync = global.wx.getSystemInfoSync
25 | global.wx.getSystemInfoSync = function() {
26 | const res = oldGetSystemInfoSync()
27 | res.SDKVersion = '2.4.1'
28 |
29 | return res
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/object.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/5/26.
3 | */
4 |
5 | function extend(destination, source, deep) {
6 | if (deep) {
7 | if (source instanceof Array) {
8 | destination = []
9 | for (var i = 0, len = source.length; i < len; i++) {
10 | destination[i] = extend({}, source[i], deep)
11 | }
12 | } else if (source && typeof source === 'object') {
13 | for (var property in source) {
14 | if (property === 'canvas') {
15 | destination[property] = extend({}, source[property])
16 | } else if (source.hasOwnProperty(property)) {
17 | destination[property] = extend({}, source[property], deep)
18 | }
19 | }
20 | } else {
21 | destination = source
22 | }
23 | } else {
24 | for (var property in source) {
25 | destination[property] = source[property]
26 | }
27 | }
28 | return destination
29 | }
30 |
31 | function clone(object, deep) {
32 | return extend({}, object, deep)
33 | }
34 |
35 | module.exports = {
36 | extend,
37 | clone
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 sugar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tools/demo/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件。",
3 | "packOptions": {
4 | "ignore": []
5 | },
6 | "setting": {
7 | "urlCheck": false,
8 | "es6": true,
9 | "postcss": true,
10 | "preloadBackgroundData": false,
11 | "minified": true,
12 | "newFeature": true,
13 | "coverView": true,
14 | "nodeModules": true,
15 | "autoAudits": false,
16 | "showShadowRootInWxmlPanel": true,
17 | "scopeDataCheck": false,
18 | "checkInvalidKey": true,
19 | "checkSiteMap": true,
20 | "uploadWithSourceMap": true,
21 | "babelSetting": {
22 | "ignore": [],
23 | "disablePlugins": [],
24 | "outputPath": ""
25 | }
26 | },
27 | "compileType": "miniprogram",
28 | "libVersion": "2.11.1",
29 | "appid": "wx29169e6dd665fd55",
30 | "projectname": "miniprogram-demo",
31 | "isGameTourist": false,
32 | "simulatorType": "wechat",
33 | "simulatorPluginLibVersion": {},
34 | "condition": {
35 | "search": {
36 | "current": -1,
37 | "list": []
38 | },
39 | "conversation": {
40 | "current": -1,
41 | "list": []
42 | },
43 | "game": {
44 | "currentL": -1,
45 | "list": []
46 | },
47 | "miniprogram": {
48 | "current": -1,
49 | "list": []
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tools/demo/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | page{
2 | padding-bottom: 500px;
3 | }
4 |
5 | .btn-group{
6 | display: flex;
7 | flex-wrap: wrap;
8 | padding: 5px;
9 | }
10 |
11 | .btn-group button{
12 | margin: 5px;
13 | }
14 |
15 | button{
16 | position: relative;
17 | border: 0rpx;
18 | display: inline-flex;
19 | align-items: center;
20 | justify-content: center;
21 | box-sizing: border-box;
22 | padding: 0 30rpx;
23 | font-size: 28rpx;
24 | height: 64rpx;
25 | line-height: 1;
26 | text-align: center;
27 | text-decoration: none;
28 | overflow: visible;
29 | margin-left: initial;
30 | transform: translate(0rpx, 0rpx);
31 | margin-right: initial;
32 | border-radius: 100px;
33 | color: #fff;
34 | box-shadow: 6rpx 6rpx 8rpx rgba(26, 26, 26, 0.2);
35 | }
36 |
37 | button.button-hover {
38 | transform: translate(1rpx, 1rpx);
39 | color: #fff;
40 | }
41 |
42 | button.orange{
43 | background-color: #f37b1d;
44 | }
45 |
46 | button.black{
47 | background-color: #666;
48 | }
49 |
50 | button.white{
51 | background-color: #fff;
52 | }
53 |
54 | button.green{
55 | background-color: #39b54a;
56 | }
57 |
58 | button.red{
59 | background-color: #e54d42;
60 | }
61 |
62 | button:after{
63 | display: none;
64 | border: 0;
65 | outline: 0;
66 | }
67 |
--------------------------------------------------------------------------------
/src/shapes/circle.class.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/6/6.
3 | */
4 | const ObjectClass = require('./object.class')
5 |
6 | const pi = Math.PI
7 |
8 | class CircleClass extends ObjectClass {
9 | constructor(options) {
10 | super()
11 |
12 | this.type = 'circle'
13 | this.radius = 0
14 | this.startAngle = 0
15 | this.endAngle = pi * 2
16 |
17 | this.initialize(options)
18 | }
19 |
20 | initialize(options) {
21 | super.initialize(options)
22 | }
23 |
24 | _set(key, value) {
25 | super._set(key, value)
26 |
27 | if (key === 'radius') {
28 | this.setRadius(value)
29 | }
30 |
31 | return this
32 | }
33 |
34 | _render(ctx) {
35 | ctx.beginPath()
36 | ctx.arc(
37 | 0,
38 | 0,
39 | this.radius,
40 | this.startAngle,
41 | this.endAngle, false)
42 | this._renderPaintInOrder(ctx)
43 | }
44 |
45 | getRadiusX() {
46 | return this.get('radius') * this.get('scaleX')
47 | }
48 |
49 | /**
50 | * 返回对象的垂直半径(根据对象的缩放比例)
51 | */
52 | getRadiusY() {
53 | return this.get('radius') * this.get('scaleY')
54 | }
55 |
56 | /**
57 | * 设置对象的半径(并更新宽度)
58 | */
59 | setRadius(value) {
60 | this.radius = value
61 | return this.set('width', value * 2).set('height', value * 2)
62 | }
63 | }
64 |
65 | module.exports = CircleClass
66 |
--------------------------------------------------------------------------------
/src/shapes/ellipse.class.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/6/6.
3 | */
4 | const ObjectClass = require('./object.class')
5 |
6 | const piBy2 = Math.PI * 2
7 |
8 | class EllipseClass extends ObjectClass {
9 | constructor(options) {
10 | super()
11 |
12 | this.type = 'ellipse'
13 | this.rx = 0
14 | this.ry = 0
15 |
16 | this.initialize(options)
17 | }
18 |
19 | initialize(options) {
20 | super.initialize(options)
21 | this.set('rx', options && options.rx || 0)
22 | this.set('ry', options && options.ry || 0)
23 | }
24 |
25 | _set(key, value) {
26 | super._set(key, value)
27 | switch (key) {
28 | case 'rx':
29 | this.rx = value
30 | this.set('width', value * 2)
31 | break;
32 |
33 | case 'ry':
34 | this.ry = value
35 | this.set('height', value * 2)
36 | break;
37 |
38 | }
39 | return this;
40 | }
41 |
42 | getRx() {
43 | return this.get('rx') * this.get('scaleX')
44 | }
45 |
46 | getRy() {
47 | return this.get('ry') * this.get('scaleY')
48 | }
49 |
50 | _render(ctx) {
51 | ctx.beginPath()
52 | ctx.save()
53 | ctx.transform(1, 0, 0, this.ry / this.rx, 0, 0)
54 | ctx.arc(
55 | 0,
56 | 0,
57 | this.rx,
58 | 0,
59 | piBy2,
60 | false);
61 | ctx.restore()
62 | this._renderPaintInOrder(ctx)
63 | }
64 | }
65 |
66 | module.exports = EllipseClass
67 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const {compareVersion} = require('./utils/index')
2 |
3 | const canvasId = 'canvas-sugarjs'
4 |
5 | Component({
6 | properties: {
7 | width: {
8 | type: Number,
9 | value: 400
10 | },
11 | height: {
12 | type: Number,
13 | value: 300
14 | },
15 | },
16 | data: {
17 | use2dCanvas: false, // 2.9.0 后可用canvas 2d 接口
18 | },
19 | lifetimes: {
20 | attached() {
21 | // const {SDKVersion, pixelRatio: dpr} = wx.getSystemInfoSync()
22 | // const use2dCanvas = compareVersion(SDKVersion, '2.9.0') >= 0
23 | // this.dpr = dpr
24 | // this.setData({use2dCanvas}, () => {
25 | // if (use2dCanvas) {
26 | // const query = this.createSelectorQuery()
27 | // query.select(`#${canvasId}`)
28 | // .fields({node: true, size: true})
29 | // .exec(res => {
30 | // const canvas = res[0].node
31 | // const ctx = canvas.getContext('2d')
32 | // canvas.width = res[0].width * dpr
33 | // canvas.height = res[0].height * dpr
34 | // // 在调用后,之后创建的路径其横纵坐标会被缩放。多次调用倍数会相乘。
35 | // ctx.scale(dpr, dpr)
36 | // this.ctx = ctx
37 | // this.canvas = canvas
38 | // })
39 | // } else {
40 | // this.ctx = wx.createCanvasContext(canvasId, this)
41 | // }
42 | // })
43 | }
44 | },
45 | methods: {}
46 | })
47 |
--------------------------------------------------------------------------------
/src/mixins/shared_methods.mixin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/5/26.
3 | */
4 | const Gradient = require('../gradient.class')
5 | const Pattern = require('../pattern.class')
6 |
7 | module.exports = {
8 | _setOptions: function (options) {
9 | for (var prop in options) {
10 | this.set(prop, options[prop]);
11 | }
12 | },
13 |
14 | _initGradient: function (filler, property) {
15 | if (filler && filler.colorStops && !(filler instanceof Gradient)) {
16 | this.set(property, new Gradient(filler));
17 | }
18 | },
19 |
20 | _initPattern: function (filler, property, callback) {
21 | if (filler && filler.source && !(filler instanceof Pattern)) {
22 | this.set(property, new Pattern(filler, callback));
23 | } else {
24 | callback && callback();
25 | }
26 | },
27 |
28 | _setObject: function (obj) {
29 | for (var prop in obj) {
30 | this._set(prop, obj[prop]);
31 | }
32 | },
33 |
34 | set: function (key, value) {
35 | if (typeof key === 'object') {
36 | this._setObject(key);
37 | } else {
38 | this._set(key, value);
39 | }
40 | return this;
41 | },
42 |
43 | _set: function (key, value) {
44 | this[key] = value;
45 | },
46 |
47 | toggle: function (property) {
48 | var value = this.get(property);
49 | if (typeof value === 'boolean') {
50 | this.set(property, !value);
51 | }
52 | return this;
53 | },
54 |
55 | get: function (property) {
56 | return this[property];
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/tools/demo/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/shapes/rect.class.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/5/26.
3 | */
4 | const ObjectClass = require('./object.class')
5 |
6 | class RectClass extends ObjectClass {
7 | constructor(options) {
8 | super();
9 | this.type = 'rect'
10 | this.rx = 0
11 | this.ry = 0
12 |
13 | this.initialize(options)
14 | }
15 |
16 | initialize(options) {
17 | super.initialize(options)
18 | this._initRxRy()
19 | }
20 |
21 | _initRxRy() {
22 | if (this.rx && !this.ry) {
23 | this.ry = this.rx
24 | } else if (this.ry && !this.rx) {
25 | this.rx = this.ry
26 | }
27 | }
28 |
29 | _render(ctx) {
30 | console.log('绘制矩形', this)
31 | let rx = this.rx ? Math.min(this.rx, this.width / 2) : 0,
32 | ry = this.ry ? Math.min(this.ry, this.height / 2) : 0,
33 | w = this.width,
34 | h = this.height,
35 | x = -this.width / 2,
36 | y = -this.height / 2,
37 | isRounded = rx !== 0 || ry !== 0,
38 | k = 1 - 0.5522847498
39 | ctx.beginPath()
40 |
41 | ctx.moveTo(x + rx, y)
42 |
43 | ctx.lineTo(x + w - rx, y)
44 | isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry)
45 |
46 | ctx.lineTo(x + w, y + h - ry)
47 | isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h)
48 |
49 | ctx.lineTo(x + rx, y + h)
50 | isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry)
51 |
52 | ctx.lineTo(x, y + ry)
53 | isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y)
54 |
55 | ctx.closePath()
56 |
57 | this._renderPaintInOrder(ctx)
58 | }
59 | }
60 |
61 | module.exports = RectClass
62 |
--------------------------------------------------------------------------------
/src/utils/string.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/5/27.
3 | */
4 |
5 | function camelize(string) {
6 | return string.replace(/-+(.)?/g, function (match, character) {
7 | return character ? character.toUpperCase() : '';
8 | });
9 | }
10 |
11 | function capitalize(string, firstLetterOnly) {
12 | return string.charAt(0).toUpperCase() +
13 | (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());
14 | }
15 |
16 | function escapeXml(string) {
17 | return string.replace(/&/g, '&')
18 | .replace(/"/g, '"')
19 | .replace(/'/g, ''')
20 | .replace(//g, '>');
22 | }
23 |
24 | function graphemeSplit(textstring) {
25 | var i = 0, chr, graphemes = [];
26 | for (i = 0, chr; i < textstring.length; i++) {
27 | if ((chr = getWholeChar(textstring, i)) === false) {
28 | continue;
29 | }
30 | graphemes.push(chr);
31 | }
32 | return graphemes;
33 | }
34 |
35 | function getWholeChar(str, i) {
36 | var code = str.charCodeAt(i);
37 |
38 | if (isNaN(code)) {
39 | return '';
40 | }
41 | if (code < 0xD800 || code > 0xDFFF) {
42 | return str.charAt(i);
43 | }
44 |
45 | if (0xD800 <= code && code <= 0xDBFF) {
46 | if (str.length <= (i + 1)) {
47 | throw 'High surrogate without following low surrogate';
48 | }
49 | var next = str.charCodeAt(i + 1);
50 | if (0xDC00 > next || next > 0xDFFF) {
51 | throw 'High surrogate without following low surrogate';
52 | }
53 | return str.charAt(i) + str.charAt(i + 1);
54 | }
55 | if (i === 0) {
56 | throw 'Low surrogate without preceding high surrogate';
57 | }
58 | var prev = str.charCodeAt(i - 1);
59 |
60 | if (0xD800 > prev || prev > 0xDBFF) {
61 | throw 'Low surrogate without preceding high surrogate';
62 | }
63 | return false;
64 | }
65 |
66 | module.exports = {
67 | camelize,
68 | capitalize,
69 | escapeXml,
70 | graphemeSplit
71 | }
72 |
--------------------------------------------------------------------------------
/src/mixins/observable.mixin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/5/28.
3 | */
4 | const {fill} = require('../utils/index');
5 |
6 | const _removeEventListener = function (eventName, handler) {
7 | if (!this.__eventListeners[eventName]) {
8 | return;
9 | }
10 | var eventListener = this.__eventListeners[eventName];
11 | if (handler) {
12 | eventListener[eventListener.indexOf(handler)] = false;
13 | } else {
14 | fill(eventListener, false);
15 | }
16 | }
17 |
18 | const on = function (eventName, handler) {
19 | if (!this.__eventListeners) {
20 | this.__eventListeners = {};
21 | }
22 | if (arguments.length === 1) {
23 | for (var prop in eventName) {
24 | this.on(prop, eventName[prop]);
25 | }
26 | } else {
27 | if (!this.__eventListeners[eventName]) {
28 | this.__eventListeners[eventName] = [];
29 | }
30 | this.__eventListeners[eventName].push(handler);
31 | }
32 | return this;
33 | }
34 |
35 | const off = function (eventName, handler) {
36 | if (!this.__eventListeners) {
37 | return this;
38 | }
39 |
40 | if (arguments.length === 0) {
41 | for (eventName in this.__eventListeners) {
42 | _removeEventListener.call(this, eventName);
43 | }
44 | } else if (arguments.length === 1 && typeof arguments[0] === 'object') {
45 | for (var prop in eventName) {
46 | _removeEventListener.call(this, prop, eventName[prop]);
47 | }
48 | } else {
49 | _removeEventListener.call(this, eventName, handler);
50 | }
51 | return this;
52 | }
53 |
54 | const fire = function (eventName, options) {
55 | if (!this.__eventListeners) {
56 | return this;
57 | }
58 |
59 | var listenersForEvent = this.__eventListeners[eventName];
60 | if (!listenersForEvent) {
61 | return this;
62 | }
63 |
64 | for (var i = 0, len = listenersForEvent.length; i < len; i++) {
65 | listenersForEvent[i] && listenersForEvent[i].call(this, options || {});
66 | }
67 | this.__eventListeners[eventName] = listenersForEvent.filter((value) => {
68 | return value !== false;
69 | });
70 | return this;
71 | }
72 |
73 | module.exports = {
74 | fire,
75 | on,
76 | off
77 | }
78 |
--------------------------------------------------------------------------------
/src/sugarjs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/5/25.
3 | */
4 | const CanvasClass = require('./canvas.class')
5 | const ObjectClass = require('./shapes/object.class')
6 | const ImageClass = require('./shapes/image.class')
7 | const TextClass = require('./shapes/text.class')
8 | const RectClass = require('./shapes/rect.class')
9 | const PolygonClass = require('./shapes/polygon.class')
10 | const TriangleClass = require('./shapes/triangle.class')
11 | const CircleClass = require('./shapes/circle.class')
12 | const EllipseClass = require('./shapes/ellipse.class')
13 | const GradientClass = require('./gradient.class')
14 | const PatternClass = require('./pattern.class')
15 | const PointClass = require('./point.class')
16 | const ColorClass = require('./color.class')
17 |
18 | const {mergeMethods} = require('./utils/index')
19 | const CommonMethods = require('./mixins/shared_methods.mixin')
20 | const Observable = require('./mixins/observable.mixin')
21 | const CanvasEvent = require('./mixins/canvas_events.mixin')
22 | const ObjectOrigin = require('./mixins/object_origin.mixin')
23 | const ObjectInteractivity = require('./mixins/object_interactivity.mixin')
24 | const ObjectGeometry = require('./mixins/object_geometry.mixin')
25 | const TextStyles = require('./mixins/text_style.mixin')
26 |
27 | const Sugar = {}
28 |
29 | mergeMethods(CanvasClass, CommonMethods)
30 | mergeMethods(CanvasClass, Observable)
31 | mergeMethods(CanvasClass, CanvasEvent)
32 | mergeMethods(ObjectClass, CommonMethods)
33 | mergeMethods(ObjectClass, Observable)
34 | mergeMethods(ObjectClass, ObjectOrigin)
35 | mergeMethods(ObjectClass, ObjectInteractivity)
36 | mergeMethods(ObjectClass, ObjectGeometry)
37 | mergeMethods(TextClass, TextStyles)
38 |
39 | Sugar.Canvas = CanvasClass
40 | Sugar.Object = ObjectClass
41 | Sugar.Image = ImageClass
42 | Sugar.Text = TextClass
43 | Sugar.Rect = RectClass
44 | Sugar.Polygon = PolygonClass
45 | Sugar.Triangle = TriangleClass
46 | Sugar.Circle = CircleClass
47 | Sugar.Ellipse = EllipseClass
48 | Sugar.Gradient = GradientClass
49 | Sugar.Pattern = PatternClass
50 | Sugar.Point = PointClass
51 | Sugar.Color = ColorClass
52 |
53 |
54 | module.exports = Sugar
55 |
--------------------------------------------------------------------------------
/tools/config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const webpack = require('webpack')
4 | const nodeExternals = require('webpack-node-externals')
5 |
6 | const isDev = process.argv.indexOf('--develop') >= 0
7 | const isWatch = process.argv.indexOf('--watch') >= 0
8 | const demoSrc = path.resolve(__dirname, './demo')
9 | const demoDist = path.resolve(__dirname, '../miniprogram_dev')
10 | const src = path.resolve(__dirname, '../src')
11 | const dev = path.join(demoDist, 'components')
12 | const dist = path.resolve(__dirname, '../miniprogram_dist')
13 |
14 | module.exports = {
15 | entry: ['index', 'sugarjs'],
16 |
17 | isDev,
18 | isWatch,
19 | srcPath: src, // 源目录
20 | distPath: isDev ? dev : dist, // 目标目录
21 |
22 | demoSrc, // demo 源目录
23 | demoDist, // demo 目标目录
24 |
25 | wxss: {
26 | less: false, // 使用 less 来编写 wxss
27 | sourcemap: false, // 生成 less sourcemap
28 | },
29 |
30 | js: {
31 | webpack: true, // 使用 webpack 来构建 js
32 | },
33 |
34 | webpack: {
35 | mode: 'production',
36 | output: {
37 | filename: '[name].js',
38 | libraryTarget: 'commonjs2',
39 | },
40 | target: 'node',
41 | externals: [nodeExternals()], // 忽略 node_modules
42 | module: {
43 | rules: [{
44 | test: /\.js$/i,
45 | use: [{
46 | loader: 'thread-loader',
47 | }, {
48 | loader: 'babel-loader',
49 | options: {
50 | cacheDirectory: true,
51 | },
52 | },
53 | // {
54 | // loader: 'eslint-loader',
55 | // }
56 | ],
57 | exclude: /node_modules/
58 | }],
59 | },
60 | resolve: {
61 | modules: [src, 'node_modules'],
62 | extensions: ['.js', '.json'],
63 | },
64 | plugins: [
65 | new webpack.DefinePlugin({}),
66 | new webpack.optimize.LimitChunkCountPlugin({maxChunks: 1}),
67 | ],
68 | optimization: {
69 | minimize: false,
70 | },
71 | devtool: 'source-map', // 生成 js sourcemap
72 | performance: {
73 | hints: 'warning',
74 | assetFilter: assetFilename => assetFilename.endsWith('.js')
75 | }
76 | },
77 |
78 | copy: ['./assets'], // 将会复制到目标目录
79 | }
80 |
--------------------------------------------------------------------------------
/publish.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/6/1.
3 | */
4 |
5 | const {execSync} = require('child_process')
6 | const semver = require('semver')
7 | const fs = require('fs')
8 | const path = require('path')
9 | const pkgPath = path.resolve(__dirname, './package.json')
10 | const pkgText = fs.readFileSync(pkgPath)
11 | const pkgObject = JSON.parse(pkgText)
12 | const yargs = require('yargs')
13 | const argv = yargs.alias('s', 'semver').argv
14 | const SEMVER_TYPE = argv.s ? argv.s : 3 // 默认为patch
15 |
16 | function getCurrentPublishedVersion() {
17 | return execSync(`npm view ${pkgObject.name} dist-tags.latest`).toString()
18 | }
19 |
20 | function getSemverType(type) {
21 | if (+type === 1) {
22 | return 'major'
23 | }
24 | if (+type === 2) {
25 | return 'minor'
26 | }
27 | if (+type === 3) {
28 | return 'patch'
29 | }
30 | }
31 |
32 | function updatePackageToGit(version) {
33 | execSync(`git checkout master`)
34 | execSync(`git add package.json`)
35 | execSync(`git commit -m "更新版本号到${version}"`)
36 | execSync(`git push -u origin master`)
37 | console.log('推送package.json更新到git 成功')
38 | }
39 |
40 | function writePackageJson(version) {
41 | // 更新package.json
42 | fs.writeFileSync(
43 | pkgPath,
44 | JSON.stringify(Object.assign(pkgObject, {
45 | version: version,
46 | }), null, 2)
47 | )
48 | }
49 |
50 | function buildJS() {
51 | execSync(`yarn clean`)
52 | execSync(`yarn dist`)
53 | }
54 |
55 | function publish() {
56 | const currentPublishedVersion = getCurrentPublishedVersion()
57 | const toPublishVersion = semver.inc(currentPublishedVersion, getSemverType(SEMVER_TYPE))
58 | console.log(`开始编译打包JS`)
59 | buildJS()
60 | console.log(`编译打包成功,已生成miniprogram_dist文件夹`)
61 | console.log(`当前线上${pkgObject.name}包的版本号为:${currentPublishedVersion}`)
62 | console.log(`开始发布npm包... 发布版本号为:${toPublishVersion}`)
63 | writePackageJson(toPublishVersion)
64 | execSync(`npm config set registry http://registry.npmjs.org/`)
65 | execSync(`npm publish`)
66 | console.log(`npm publish发布成功`)
67 | execSync(`npm config set registry https://registry.npm.taobao.org`)
68 | updatePackageToGit(toPublishVersion)
69 | }
70 |
71 | function run() {
72 | publish()
73 | }
74 |
75 | run()
76 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "miniprogram-canvas-sugarjs",
3 | "version": "1.0.4",
4 | "description": "打造一个致力于微信小程序的Canvas库,类似于H5原生JS Canvas库-FabricJS",
5 | "main": "miniprogram_dist/sugarjs.js",
6 | "scripts": {
7 | "dev": "gulp dev --develop",
8 | "watch": "gulp watch --develop --watch",
9 | "build": "gulp",
10 | "dist": "npm run build",
11 | "clean-dev": "gulp clean --develop",
12 | "clean": "gulp clean",
13 | "test": "jest --bail",
14 | "test-debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --bail",
15 | "coverage": "jest ./test/* --coverage --bail",
16 | "lint": "eslint \"src/**/*.js\" --fix",
17 | "lint-tools": "eslint \"tools/**/*.js\" --rule \"import/no-extraneous-dependencies: false\" --fix"
18 | },
19 | "miniprogram": "miniprogram_dist",
20 | "jest": {
21 | "testEnvironment": "jsdom",
22 | "testURL": "https://jest.test",
23 | "collectCoverageFrom": [
24 | "miniprogram_dist/**/*.js"
25 | ],
26 | "moduleDirectories": [
27 | "node_modules",
28 | "miniprogram_dist"
29 | ]
30 | },
31 | "repository": {
32 | "type": "git",
33 | "url": ""
34 | },
35 | "author": "sugar ",
36 | "license": "MIT",
37 | "devDependencies": {
38 | "@typescript-eslint/eslint-plugin": "^2.28.0",
39 | "@typescript-eslint/parser": "^2.28.0",
40 | "babel-core": "^6.26.3",
41 | "babel-loader": "^7.1.5",
42 | "babel-plugin-module-resolver": "^3.2.0",
43 | "babel-preset-env": "^1.7.0",
44 | "colors": "^1.3.1",
45 | "eslint": "^5.14.1",
46 | "eslint-config-airbnb-base": "13.1.0",
47 | "eslint-loader": "^2.1.2",
48 | "eslint-plugin-import": "^2.16.0",
49 | "eslint-plugin-node": "^7.0.1",
50 | "eslint-plugin-promise": "^3.8.0",
51 | "gulp": "^4.0.0",
52 | "gulp-clean": "^0.4.0",
53 | "gulp-if": "^2.0.2",
54 | "gulp-install": "^1.1.0",
55 | "gulp-less": "^4.0.1",
56 | "gulp-rename": "^1.4.0",
57 | "gulp-sourcemaps": "^2.6.5",
58 | "jest": "^23.5.0",
59 | "miniprogram-api-typings": "^2.10.3-1",
60 | "miniprogram-simulate": "^1.2.0",
61 | "thread-loader": "^2.1.3",
62 | "through2": "^2.0.3",
63 | "ts-loader": "^7.0.0",
64 | "typescript": "^3.8.3",
65 | "vinyl": "^2.2.0",
66 | "webpack": "^4.29.5",
67 | "webpack-node-externals": "^1.7.2"
68 | },
69 | "dependencies": {}
70 | }
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'extends': [
3 | 'airbnb-base',
4 | 'plugin:promise/recommended'
5 | ],
6 | 'parser': '@typescript-eslint/parser',
7 | 'plugins': ['@typescript-eslint'],
8 | 'parserOptions': {
9 | 'ecmaVersion': 9,
10 | 'ecmaFeatures': {
11 | 'jsx': false
12 | },
13 | 'sourceType': 'module'
14 | },
15 | 'env': {
16 | 'es6': true,
17 | 'node': true,
18 | 'jest': true
19 | },
20 | 'plugins': [
21 | 'import',
22 | 'node',
23 | 'promise'
24 | ],
25 | 'rules': {
26 | 'arrow-parens': 'off',
27 | 'comma-dangle': [
28 | 'error',
29 | 'only-multiline'
30 | ],
31 | 'complexity': ['error', 10],
32 | 'func-names': 'off',
33 | 'global-require': 'off',
34 | 'handle-callback-err': [
35 | 'error',
36 | '^(err|error)$'
37 | ],
38 | 'import/no-unresolved': [
39 | 'error',
40 | {
41 | 'caseSensitive': true,
42 | 'commonjs': true,
43 | 'ignore': ['^[^.]']
44 | }
45 | ],
46 | 'import/prefer-default-export': 'off',
47 | 'linebreak-style': 'off',
48 | 'no-catch-shadow': 'error',
49 | 'no-continue': 'off',
50 | 'no-div-regex': 'warn',
51 | 'no-else-return': 'off',
52 | 'no-param-reassign': 'off',
53 | 'no-plusplus': 'off',
54 | 'no-shadow': 'off',
55 | 'no-multi-assign': 'off',
56 | 'no-underscore-dangle': 'off',
57 | 'node/no-deprecated-api': 'error',
58 | 'node/process-exit-as-throw': 'error',
59 | 'object-curly-spacing': [
60 | 'error',
61 | 'never'
62 | ],
63 | 'operator-linebreak': [
64 | 'error',
65 | 'after',
66 | {
67 | 'overrides': {
68 | ':': 'before',
69 | '?': 'before'
70 | }
71 | }
72 | ],
73 | 'prefer-arrow-callback': 'off',
74 | 'prefer-destructuring': 'off',
75 | 'prefer-template': 'off',
76 | 'quote-props': [
77 | 1,
78 | 'as-needed',
79 | {
80 | 'unnecessary': true
81 | }
82 | ],
83 | 'semi': [
84 | 'error',
85 | 'never'
86 | ],
87 | 'no-await-in-loop': 'off',
88 | 'no-restricted-syntax': 'off',
89 | 'promise/always-return': 'off',
90 | },
91 | 'globals': {
92 | 'window': true,
93 | 'document': true,
94 | 'App': true,
95 | 'Page': true,
96 | 'Component': true,
97 | 'Behavior': true,
98 | 'wx': true,
99 | 'getCurrentPages': true,
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # miniprogram-canvas-sugarjs
2 |
3 | [](https://www.npmjs.com/package/miniprogram-canvas-sugarjs)
4 |
5 | > 使用此组件需要依赖依赖开发者工具的 npm 构建。具体详情可查阅[微信小程序官方npm文档](https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html)。
6 |
7 | ## 介绍
8 |
9 | 打造一个致力于微信小程序的Canvas库,类似于H5原生JS Canvas库 - [FabricJS](http://fabricjs.com/)
10 |
11 |
12 |
13 |
14 |
15 | ## 开发进度
16 |
17 | 开发中...
18 |
19 | ## 使用方法
20 |
21 | 1. 安装组件
22 |
23 | ```
24 | npm install --save miniprogram-canvas-sugarjs
25 | ```
26 |
27 | 2. .wxml
28 |
29 | ```html
30 |
38 | ```
39 |
40 | 3. 在.js文件中引用
41 |
42 | ```js
43 | const sugar = require('miniprogram-canvas-sugarjs')
44 |
45 | Page({
46 | data: {
47 | width: windowWidth,
48 | height: 500,
49 | },
50 | onReady() {
51 | const query = wx.createSelectorQuery()
52 | query.select(`#sugarjs`).fields({node: true, size: true}).exec(res => {
53 | const canvas = res[0].node
54 | this.sugar = new sugar.Canvas({
55 | canvas: canvas,
56 | width: this.data.width,
57 | height: this.data.height,
58 | backgroundColor: 'skyblue'
59 | })
60 | })
61 | }
62 | })
63 | ```
64 |
65 |
66 |
67 | ### 功能清单
68 |
69 | 1. canvas主体
70 | - [x] 初始化(宽高、背景)
71 | - [ ] 其他...
72 |
73 | 2. 图层类
74 | - [x] 基类ObjectClass
75 | - [x] 图片ImageClass
76 | - [x] 文本TextClass
77 | - [x] 矩形RectClass
78 | - [x] 三角形TriangleClass
79 | - [x] 多边形PolygonClass
80 | - [x] 圆CircleClass
81 | - [x] 椭圆EllipseClass
82 | - [ ] 直线LineClass
83 | - [ ] 群组GroupClass
84 | - [ ] 其他...
85 |
86 |
87 | 3. 操作
88 | - [x] 增删
89 | - [x] 点击图层进入选中状态(显示图层边框控件)
90 | - [x] 拖动
91 | - [x] 缩放
92 | - [x] 旋转
93 | - [x] 翻转
94 | - [ ] 图层层级管理(上移、下移、置顶、置底)
95 | - [ ] 文本内容编辑
96 | - [ ] 其他...
97 |
98 | 4. 事件监听
99 | - [x] canvas初始化周期事件
100 | - [x] 手指触摸事件
101 | - [ ] 清单3中的操作事件的监听
102 | - [ ] 其他...
103 |
104 | 5. 拓展、增强功能
105 | - [ ] 状态存储(撤销undo、恢复redo)
106 | - [ ] 导入、导出canvas数据
107 | - [x] toDataURL生成图片
108 | - [ ] 手势缩放、旋转
109 | - [ ] 动画
110 | - [ ] 其他...
111 |
112 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/5/25.
3 | */
4 |
5 | /**
6 | * 小程序版本库版本号比较
7 | * @param v1
8 | * @param v2
9 | * @returns {number}
10 | */
11 | const compareVersion = (v1, v2) => {
12 | v1 = v1.split('.')
13 | v2 = v2.split('.')
14 | const len = Math.max(v1.length, v2.length)
15 | while (v1.length < len) {
16 | v1.push('0')
17 | }
18 | while (v2.length < len) {
19 | v2.push('0')
20 | }
21 | for (let i = 0; i < len; i++) {
22 | const num1 = parseInt(v1[i], 10)
23 | const num2 = parseInt(v2[i], 10)
24 |
25 | if (num1 > num2) {
26 | return 1
27 | } else if (num1 < num2) {
28 | return -1
29 | }
30 | }
31 |
32 | return 0
33 | }
34 |
35 |
36 | let slice = Array.prototype.slice;
37 |
38 | function invoke(array, method) {
39 | let args = slice.call(arguments, 2), result = [];
40 | for (let i = 0, len = array.length; i < len; i++) {
41 | result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]);
42 | }
43 | return result;
44 | }
45 |
46 | function max(array, byProperty) {
47 | return find(array, byProperty, function (value1, value2) {
48 | return value1 >= value2;
49 | });
50 | }
51 |
52 | function min(array, byProperty) {
53 | return find(array, byProperty, function (value1, value2) {
54 | return value1 < value2;
55 | });
56 | }
57 |
58 | function fill(array, value) {
59 | let k = array.length;
60 | while (k--) {
61 | array[k] = value;
62 | }
63 | return array;
64 | }
65 |
66 | function find(array, byProperty, condition) {
67 | if (!array || array.length === 0) {
68 | return;
69 | }
70 |
71 | let i = array.length - 1,
72 | result = byProperty ? array[i][byProperty] : array[i];
73 | if (byProperty) {
74 | while (i--) {
75 | if (condition(array[i][byProperty], result)) {
76 | result = array[i][byProperty];
77 | }
78 | }
79 | } else {
80 | while (i--) {
81 | if (condition(array[i], result)) {
82 | result = array[i];
83 | }
84 | }
85 | }
86 | return result;
87 | }
88 |
89 | function toFixed(number, fractionDigits) {
90 | return parseFloat(Number(number).toFixed(fractionDigits));
91 | }
92 |
93 | function mergeMethods(a, b) {
94 | for (const prop in b) {
95 | if (b.hasOwnProperty(prop)) {
96 | a.prototype[prop] = b[prop]
97 | }
98 | }
99 | return a
100 | }
101 |
102 | module.exports = {
103 | compareVersion,
104 | fill,
105 | invoke,
106 | min,
107 | max,
108 | toFixed,
109 | mergeMethods
110 | }
111 |
--------------------------------------------------------------------------------
/src/shapes/polygon.class.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/6/7.
3 | */
4 | const ObjectClass = require('./object.class')
5 | const {
6 | min,
7 | max,
8 | } = require('../utils/index')
9 |
10 | class PolygonClass extends ObjectClass {
11 | constructor(points, options) {
12 | super()
13 |
14 | this.type = 'polygon'
15 | this.points = null
16 |
17 | this.initialize(points, options)
18 | }
19 |
20 | initialize(points, options) {
21 | options = options || {}
22 | this.points = points || []
23 | super.initialize(options)
24 | this._setPositionDimensions(options)
25 | }
26 |
27 | _setPositionDimensions(options) {
28 | let calcDim = this._calcDimensions(options), correctLeftTop
29 | this.width = calcDim.width
30 | this.height = calcDim.height
31 | if (!options.fromSVG) {
32 | correctLeftTop = this.translateToGivenOrigin(
33 | {x: calcDim.left - this.strokeWidth / 2, y: calcDim.top - this.strokeWidth / 2},
34 | 'left',
35 | 'top',
36 | this.originX,
37 | this.originY
38 | )
39 | }
40 | if (typeof options.left === 'undefined') {
41 | // this.left = options.fromSVG ? calcDim.left : correctLeftTop.x
42 | this.left = correctLeftTop.x
43 | }
44 | if (typeof options.top === 'undefined') {
45 | // this.top = options.fromSVG ? calcDim.top : correctLeftTop.y
46 | this.top = correctLeftTop.y
47 | }
48 | this.pathOffset = {
49 | x: calcDim.left + this.width / 2,
50 | y: calcDim.top + this.height / 2
51 | }
52 | }
53 |
54 | _calcDimensions() {
55 | let points = this.points,
56 | minX = min(points, 'x') || 0,
57 | minY = min(points, 'y') || 0,
58 | maxX = max(points, 'x') || 0,
59 | maxY = max(points, 'y') || 0,
60 | width = (maxX - minX),
61 | height = (maxY - minY)
62 |
63 | return {
64 | left: minX,
65 | top: minY,
66 | width: width,
67 | height: height
68 | }
69 | }
70 |
71 | commonRender(ctx) {
72 | let point, len = this.points.length,
73 | x = this.pathOffset.x,
74 | y = this.pathOffset.y
75 |
76 | if (!len || isNaN(this.points[len - 1].y)) {
77 | return false
78 | }
79 | ctx.beginPath()
80 | ctx.moveTo(this.points[0].x - x, this.points[0].y - y)
81 | for (let i = 0; i < len; i++) {
82 | point = this.points[i]
83 | ctx.lineTo(point.x - x, point.y - y)
84 | }
85 | return true
86 | }
87 |
88 | _render(ctx) {
89 | if (!this.commonRender(ctx)) {
90 | return;
91 | }
92 | ctx.closePath()
93 | this._renderPaintInOrder(ctx)
94 | }
95 |
96 | complexity() {
97 | return this.get('points').length
98 | }
99 | }
100 |
101 | module.exports = PolygonClass
102 |
--------------------------------------------------------------------------------
/src/pattern.class.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sugar on 2020/5/26.
3 | */
4 | const ObjectClass = require('./shapes/object.class')
5 | const {toFixed} = require('./utils/index')
6 | const {populateWithProperties} = require('./utils/misc')
7 |
8 | class PatternClass {
9 | constructor(options) {
10 | this.repeat = 'repeat'
11 | this.offsetX = 0
12 | this.offsetY = 0
13 | this.initialize(options)
14 | }
15 |
16 |
17 | initialize(options, callback) {
18 | options || (options = {});
19 |
20 | this.id = ObjectClass.__uid++;
21 | this.setOptions(options);
22 | if (!options.source || (options.source && typeof options.source !== 'string')) {
23 | callback && callback(this);
24 | return;
25 | } else {
26 | // img src string
27 | let _this = this;
28 | // this.source = createImage();
29 | // loadImage(options.source, function (img, isError) {
30 | // _this.source = img;
31 | // callback && callback(_this, isError);
32 | // }, null, this.crossOrigin);
33 | }
34 | }
35 |
36 | toObject(propertiesToInclude) {
37 | let NUM_FRACTION_DIGITS = 2,
38 | source, object;
39 |
40 | //
element
41 | if (typeof this.source.src === 'string') {
42 | source = this.source.src;
43 | }
44 | //