├── .gitignore
├── src
└── fabric
│ ├── pencil_brush.coffee
│ ├── canvas_with_viewport.coffee
│ ├── viewport.coffee
│ └── controls_extension
│ └── object.coffee
├── Cakefile
├── LICENSE
├── README.md
├── example
└── index.html
└── dist
└── fabricjs_viewport.js
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/*
--------------------------------------------------------------------------------
/src/fabric/pencil_brush.coffee:
--------------------------------------------------------------------------------
1 | old = fabric.PencilBrush.prototype._render
2 |
3 | fabric.PencilBrush.prototype._render = () ->
4 | if @canvas.viewport
5 | ctx = this.canvas.contextTop
6 | ctx.save()
7 | ctx.scale(@canvas.viewport.zoom, @canvas.viewport.zoom)
8 | ctx.translate(@canvas.viewport.translate().x, @canvas.viewport.translate().y)
9 | old.apply(this)
10 | ctx.restore()
11 | else
12 | old.apply(this)
13 |
--------------------------------------------------------------------------------
/Cakefile:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | {exec} = require 'child_process'
3 |
4 | appFiles = [
5 | 'fabric/controls_extension/object'
6 | 'fabric/viewport'
7 | 'fabric/pencil_brush'
8 | 'fabric/canvas_with_viewport'
9 | ]
10 |
11 | appFileName = "fabricjs_viewport"
12 |
13 | task 'build', 'Build single application file from source files', ->
14 | appContents = new Array remaining = appFiles.length
15 | for file, index in appFiles then do (file, index) ->
16 | fs.readFile "src/#{file}.coffee", 'utf8', (err, fileContents) ->
17 | throw err if err
18 | appContents[index] = fileContents
19 | process() if --remaining is 0
20 | process = ->
21 | fs.writeFile "dist/#{appFileName}.coffee", appContents.join('\n\n'), 'utf8', (err) ->
22 | throw err if err
23 | exec "coffee --compile dist/#{appFileName}.coffee", (err, stdout, stderr) ->
24 | throw err if err
25 | console.log stdout + stderr
26 | fs.unlink "dist/#{appFileName}.coffee", (err) ->
27 | throw err if err
28 | console.log 'Done.'
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 SoftwareBrothers.co
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 |
--------------------------------------------------------------------------------
/src/fabric/canvas_with_viewport.coffee:
--------------------------------------------------------------------------------
1 | class fabric.CanvasWithViewport extends fabric.Canvas
2 | constructor: () ->
3 | @viewport = new fabric.Viewport(@)
4 | @isGrabMode = false
5 | @_isCurrentlyGrabbing = false
6 | super
7 |
8 | setZoom: (zoom) ->
9 | @viewport.setZoom(zoom)
10 | @renderAll()
11 |
12 |
13 | _onMouseMoveInGrabMode: (e) ->
14 | if @_isCurrentlyGrabbing
15 | @viewport.grab(e)
16 | @renderAll()
17 | true
18 |
19 | _onMouseDownInGrabMode: (e) ->
20 | @_isCurrentlyGrabbing = true
21 | @viewport.grabStart(e)
22 |
23 |
24 | _onMouseUpInGrabMode: (e) ->
25 | if @_isCurrentlyGrabbing
26 | @viewport.grabEnd(e)
27 | @_isCurrentlyGrabbing = false
28 | @renderAll()
29 | true
30 |
31 | _draw: (ctx, object) ->
32 | ctx.save()
33 | ctx.scale(@viewport.zoom, @viewport.zoom)
34 | ctx.translate(@viewport.translate().x, @viewport.translate().y)
35 | super
36 | ctx.restore()
37 |
38 | _drawSelection: () ->
39 | ctx = this.contextTop
40 | ctx.save()
41 | ctx.scale(@viewport.zoom,@viewport.zoom)
42 | ctx.translate(@viewport.translate().x, @viewport.translate().y)
43 | super
44 | ctx.restore()
45 |
46 | __onMouseMove: (e) ->
47 | if @isGrabMode
48 | @_onMouseMoveInGrabMode(e)
49 | return
50 | super(@viewport.transform(e))
51 |
52 | __onMouseDown: (e) ->
53 | if @isGrabMode
54 | @_onMouseDownInGrabMode(e)
55 | return
56 | super(@viewport.transform(e))
57 |
58 | __onMouseUp: (e) ->
59 | if @isGrabMode
60 | @_onMouseUpInGrabMode(e)
61 | return
62 | super(@viewport.transform(e))
63 |
--------------------------------------------------------------------------------
/src/fabric/viewport.coffee:
--------------------------------------------------------------------------------
1 | class fabric.Viewport
2 | constructor: (canvas) ->
3 | @i = 0
4 | @position = new fabric.Point(0,0)
5 | @zoom = 1
6 | @canvas = canvas
7 | @_resetGrab()
8 | true
9 |
10 | grabStart: (e) ->
11 | @grabStartPointer = @canvas.getPointer(e)
12 |
13 | grab: (e) ->
14 | @grabPointer = @canvas.getPointer(e)
15 |
16 | grabEnd: (e) ->
17 | diff = new fabric.Point(@canvas.getPointer(e).x - @grabStartPointer.x, @canvas.getPointer(e).y - @grabStartPointer.y)
18 | @position = @position.add(diff)
19 | @_resetGrab()
20 |
21 | _resetGrab: () ->
22 | @grabStartPointer = {x: 0, y: 0}
23 | @grabPointer = {x: 0, y: 0}
24 |
25 | setZoom: (newZoom) ->
26 | @_adjustPositionAfterZoom(newZoom)
27 | @zoom = newZoom
28 |
29 | _adjustPositionAfterZoom: (newZoom) ->
30 | halfWidth = @canvas.width / 2
31 | halfHeight = @canvas.height / 2
32 | k = newZoom / @zoom
33 | @position.x = halfWidth - k * (halfWidth - @position.x)
34 | @position.y = halfHeight - k * (halfHeight - @position.y)
35 |
36 | translate: () ->
37 | {
38 | x: (@position.x + @grabPointer.x - @grabStartPointer.x)/@zoom
39 | y: (@position.y + @grabPointer.y - @grabStartPointer.y)/@zoom
40 | }
41 |
42 | transform: (event) ->
43 | touchProp = if event.type == 'touchend' then 'changedTouches' else 'touches'
44 |
45 | if event[touchProp] && event[touchProp][0]
46 | ex = {}
47 | ex[touchProp] = _.map(event[touchProp], (t) => @_transformEventParams(t) )
48 | $.extend($.Event(event.type), ex)
49 | else
50 | $.extend($.Event(event.type), @_transformEventParams(event))
51 |
52 | _transformEventParams: (e) ->
53 | offsetTop = @canvas.wrapperEl.getBoundingClientRect().top
54 | offsetLeft = @canvas.wrapperEl.getBoundingClientRect().left
55 | {
56 | which: 1
57 | clientX: (e.clientX-offsetLeft)/@zoom+offsetLeft - @translate().x,
58 | clientY: (e.clientY-offsetTop)/@zoom+offsetTop - @translate().y,
59 | pageX: e.pageX - @translate().x,
60 | pageY: e.pageY - @translate().y,
61 | screenX: e.screenX - @translate().x,
62 | screenY: e.screenY - @translate().y
63 | }
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fabricjs-viewport
2 |
3 | Simple implementation of viewport and zoom in fabricjs.
4 |
5 | 1. Doesn't change data model, so none of the objects on your canvas is changed after zooming/grabbing
6 | 2. Supports touch devices
7 | 3. Supports free drawing mode
8 |
9 | First [see an example](http://softwarebrothers.github.io/fabricjs-viewport/)
10 |
11 | ### How to use it
12 |
13 | Currently it depends on jquery, however I plan to fix it soon.
14 | In the HEAD of your HTML file include jQuery, fabricjs and fabricjs-viewport. You can find fabricjs-viewport.js in this repo in the /dist/ directory.
15 |
16 | You can invclude those libraries from CDNs (order matters):
17 |
18 | ```javascript
19 |
20 |
21 |
22 | ```
23 |
24 | Next if you need to use zooming or changing viewport in your project just use fabric.CanvasWithViewport insteed of fabric.Canvas
25 |
26 | var c = new fabric.CanvasWithViewport("id-of-your-canvas");
27 |
28 | Now you are be able to:
29 |
30 | Turn on grabbing mode:
31 |
32 | ```javascript
33 | c.isGrabMode = true;
34 | ```
35 |
36 | With this you can change the viewport with drag and drop on the canvas.
37 |
38 | You also can zoom in and out:
39 |
40 | ```javascript
41 | c.setZoom(c.viewport.zoom*1.1); // zoom in by 10%
42 | ```
43 |
44 | ## OpenSource SoftwareBrothers community
45 |
46 | - [Join the community](https://join.slack.com/t/adminbro/shared_invite/zt-czfb79t1-0U7pn_KCqd5Ts~lbJK0_RA) to get help and be inspired.
47 | - subscribe to our [newsletter](https://www.getrevue.co/profile/adminbro)
48 |
49 | ## License
50 |
51 | fabricjs-viewport is Copyright © 2018 SoftwareBrothers.co. It is free software, and may be redistributed under the terms specified in the [LICENSE](LICENSE.md) file.
52 |
53 | ## About SoftwareBrothers.co
54 |
55 |
56 |
57 |
58 | We are a software company who provides web and mobile development and UX/UI services, friendly team that helps clients from all over the world to transform their businesses and create astonishing products.
59 |
60 | * We are available for [hire](https://softwarebrothers.co/contact).
61 | * If you want to work for us - checkout the [career page](https://softwarebrothers.co/career).
62 |
--------------------------------------------------------------------------------
/src/fabric/controls_extension/object.coffee:
--------------------------------------------------------------------------------
1 | _drawControl = fabric.Object.prototype._drawControl
2 |
3 | fabric.Object.prototype._drawControl = (control, ctx, methodName, left, top) ->
4 | zoom = @canvas.viewport?.zoom || 1
5 | ctx.lineWidth = 1 / Math.max(@scaleX, @scaleY) / zoom
6 | _drawControl.apply(@, [control, ctx, methodName, left, top])
7 |
8 | drawControls = fabric.Object.prototype.drawControls
9 |
10 | fabric.Object.prototype.drawControls = (ctx) ->
11 | zoom = @canvas?.viewport?.zoom || 1
12 | @cornerSize = @cornerSize / zoom
13 | ret = drawControls.apply(@, [ctx])
14 | @cornerSize = @cornerSize * zoom
15 | ret
16 |
17 | _setCornerCoords = fabric.Object.prototype._setCornerCoords
18 |
19 | fabric.Object.prototype._setCornerCoords = () ->
20 | zoom = @canvas?.viewport?.zoom || 1
21 | @cornerSize = @cornerSize / zoom
22 | ret = _setCornerCoords.apply(@)
23 | @cornerSize = @cornerSize * zoom
24 | ret
25 |
26 |
27 | fabric.Object.prototype.drawBorders = (ctx) ->
28 | return @ unless this.hasBorders
29 | zoom = @canvas?.viewport?.zoom || 1
30 |
31 | padding = this.padding
32 | padding2 = padding * 2
33 | strokeWidth = ~~(this.strokeWidth / 2) * 2
34 |
35 | ctx.save()
36 |
37 | ctx.globalAlpha = if this.isMoving then this.borderOpacityWhenMoving else 1
38 | ctx.strokeStyle = this.borderColor
39 |
40 | scaleX = 1 / this._constrainScale(this.scaleX)
41 | scaleY = 1 / this._constrainScale(this.scaleY)
42 |
43 | ctx.lineWidth = 1 / this.borderScaleFactor / zoom;
44 |
45 | ctx.scale(scaleX, scaleY)
46 |
47 | w = this.getWidth()
48 | h = this.getHeight()
49 |
50 | ctx.strokeRect(
51 | (-(w / 2) - padding - strokeWidth / 2 * this.scaleX) - 0.5 / zoom,
52 | (-(h / 2) - padding - strokeWidth / 2 * this.scaleY) - 0.5 / zoom,
53 | (w + padding2 + strokeWidth * this.scaleX) + 1 / zoom,
54 | (h + padding2 + strokeWidth * this.scaleY) + 1 / zoom
55 | )
56 |
57 | if this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls
58 | rotateHeight = (
59 | if this.flipY then h + ((strokeWidth * this.scaleY) + (padding * 2))*zoom else -h - ((strokeWidth * this.scaleY) + (padding * 2))*zoom
60 | ) / 2
61 |
62 | ctx.beginPath()
63 | ctx.moveTo(0, rotateHeight)
64 | ctx.lineTo(0, rotateHeight + (if this.flipY then this.rotatingPointOffset else -this.rotatingPointOffset))
65 | ctx.closePath()
66 | ctx.stroke()
67 |
68 | ctx.restore()
69 | return @
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |