Fabric JS:缩放时捕捉准则未正确定位
Fabric JS: Snapping guidelines not correctly positioned when zoomed
对于我的 fabric.js 项目:我正在尝试设置对象捕捉和对齐准则。对于捕捉,这意味着当用户四处拖动对象时,如果对象的任何边缘接近与另一个对象边缘对齐,它将捕捉到位。在此期间,指南会作为用户的视觉助手出现。
到目前为止,我正在实施现有的工作,这些工作由各种 fabric.js 贡献者完成,可在此处找到:
centering_guidelines.js & aligning_guidelines.js.
作品:在默认缩放 (1) 下,对象捕捉和对齐准则效果很好!
失败:放大(放大或缩小)时,视觉指南出现在错误的位置,但捕捉保持正确的功能。
代码示例:四处移动对象。在默认缩放下,捕捉和指南效果很好。更改缩放级别(使用鼠标滚轮)并注意指南未正确定位,但捕捉工作正常。
示例 1:简单
原始库按原样加载;简单的演示。
https://codepen.io/MarsAndBack/pen/ZEQMXoM
示例 2:详细
内联复制粘贴原始库,并进行修改以帮助调查。
https://codepen.io/MarsAndBack/pen/LYGJGoq
注意:Codepen 有完整的代码。
// ==========================================
// SETUP
// ==========================================
const canvas = new fabric.Canvas("myCanvas")
canvas.backgroundColor = "#222222";
var lastClientX = 0
var lastClientY = 0
var state = "default"
const outer = null
const box1 = null
const box2 = null
this.centerLine_horizontal = ""
this.centerLine_vertical = ""
this.alignmentLines_horizontal = ""
this.alignmentLines_vertical = ""
fabric.Object.prototype.set({
cornerSize: 15,
cornerStyle: 'circle',
cornerColor: '#ffffff',
transparentCorners: false
})
setupObjects()
updateInfo(canvas)
function setupObjects() {
this.outer = new fabric.Rect({
width: canvas.getWidth(),
height: canvas.getHeight(),
top: 20,
left: 20,
stroke: '#ffffff',
evented: false,
selectable: false
})
this.box1 = new fabric.Rect({
width: 240,
height: 100,
top: 20,
left: 20,
fill: '#fff28a',
myType: "box"
})
this.box2 = new fabric.Rect({
width: 240,
height: 100,
top: 140,
left: 20,
fill: '#ff8a8a',
myType: "box"
})
this.box3 = new fabric.Rect({
width: 100,
height: 160,
top: 20,
left: 280,
fill: '#cf8aff',
myType: "box"
})
canvas.add(this.outer)
this.outer.center()
canvas.add(this.box1)
canvas.add(this.box2)
canvas.add(this.box3)
let allBoxes = new fabric.ActiveSelection(canvas.getObjects().filter(obj => obj.myType == "box"), {
canvas: canvas
})
allBoxes.center()
allBoxes.destroy()
}
function updateInfo() {
let info_zoom = document.getElementById('info_zoom')
let info_vptTop = document.getElementById('info_vptTop')
let info_vptLeft = document.getElementById('info_vptLeft')
let info_centerLine_horizontal = document.getElementById('info_centerLine_horizontal')
let info_centerLine_vertical = document.getElementById('info_centerLine_vertical')
let info_alignmentLines_horizontal = document.getElementById('info_alignmentLines_horizontal')
let info_alignmentLines_vertical = document.getElementById('info_alignmentLines_vertical')
info_zoom.innerHTML = canvas.getZoom().toFixed(2)
info_vptTop.innerHTML = Math.round(canvas.viewportTransform[5])
info_vptLeft.innerHTML = Math.round(canvas.viewportTransform[4])
info_centerLine_horizontal.innerHTML = this.centerLine_horizontal
info_centerLine_vertical.innerHTML = this.centerLine_vertical
info_alignmentLines_horizontal.innerHTML = this.alignmentLines_horizontal
info_alignmentLines_vertical.innerHTML = this.alignmentLines_vertical
}
// ------------------------------------
// Reset
// ------------------------------------
let resetButton = document.getElementById('reset')
resetButton.addEventListener('click', function() {
reset()
}, false)
function reset() {
canvas.remove(...canvas.getObjects())
setupObjects()
canvas.setViewportTransform([1, 0, 0, 1, 0, 0])
updateInfo()
}
// ------------------------------------
// ==========================================
// MOUSE INTERACTIONS
// ==========================================
// MOUSEWHEEL ZOOM
canvas.on('mouse:wheel', (opt) => {
let delta = 0
// -------------------------------
// WHEEL RESOLUTION
let wheelDelta = opt.e.wheelDelta
let deltaY = opt.e.deltaY
// CHROME WIN/MAC | SAFARI 7 MAC | OPERA WIN/MAC | EDGE
if (wheelDelta) {
delta = -wheelDelta / 120
}
// FIREFOX WIN / MAC | IE
if (deltaY) {
deltaY > 0 ? delta = 1 : delta = -1
}
// -------------------------------
let pointer = canvas.getPointer(opt.e)
let zoom = canvas.getZoom()
zoom = zoom - delta / 10
// limit zoom in
if (zoom > 4) zoom = 4
// limit zoom out
if (zoom < 0.2) {
zoom = 0.2
}
//canvas.zoomToPoint({
// x: opt.e.offsetX,
// y: opt.e.offsetY
//}, zoom)
canvas.zoomToPoint(
new fabric.Point(canvas.width / 2, canvas.height / 2),
zoom);
opt.e.preventDefault()
opt.e.stopPropagation()
canvas.renderAll()
canvas.calcOffset()
updateInfo(canvas)
})
initCenteringGuidelines(canvas)
initAligningGuidelines(canvas)
// ==========================================
// CANVAS CENTER SNAPPING & ALIGNMENT GUIDELINES
// ==========================================
// ORIGINAL:
// https://github.com/fabricjs/fabric.js/blob/master/lib/centering_guidelines.js
/**
* Augments canvas by assigning to `onObjectMove` and `onAfterRender`.
* This kind of sucks because other code using those methods will stop functioning.
* Need to fix it by replacing callbacks with pub/sub kind of subscription model.
* (or maybe use existing fabric.util.fire/observe (if it won't be too slow))
*/
function initCenteringGuidelines(canvas) {
let canvasWidth = canvas.getWidth(),
canvasHeight = canvas.getHeight(),
canvasWidthCenter = canvasWidth / 2,
canvasHeightCenter = canvasHeight / 2,
canvasWidthCenterMap = {},
canvasHeightCenterMap = {},
centerLineMargin = 4,
centerLineColor = 'purple',
centerLineWidth = 2,
ctx = canvas.getSelectionContext(),
viewportTransform
for (let i = canvasWidthCenter - centerLineMargin, len = canvasWidthCenter + centerLineMargin; i <= len; i++) {
canvasWidthCenterMap[Math.round(i)] = true
}
for (let i = canvasHeightCenter - centerLineMargin, len = canvasHeightCenter + centerLineMargin; i <= len; i++) {
canvasHeightCenterMap[Math.round(i)] = true
}
function showVerticalCenterLine() {
showCenterLine(canvasWidthCenter + 0.5, 0, canvasWidthCenter + 0.5, canvasHeight)
}
function showHorizontalCenterLine() {
showCenterLine(0, canvasHeightCenter + 0.5, canvasWidth, canvasHeightCenter + 0.5)
}
function showCenterLine(x1, y1, x2, y2) {
ctx.save()
ctx.strokeStyle = centerLineColor
ctx.lineWidth = centerLineWidth
ctx.beginPath()
ctx.moveTo(x1 * viewportTransform[0], y1 * viewportTransform[3])
ctx.lineTo(x2 * viewportTransform[0], y2 * viewportTransform[3])
ctx.stroke()
ctx.restore()
}
let afterRenderActions = [],
isInVerticalCenter,
isInHorizontalCenter
canvas.on('mouse:down', () => {
isInVerticalCenter = isInHorizontalCenter = null
this.centerLine_horizontal = ""
this.centerLine_vertical = ""
updateInfo()
viewportTransform = canvas.viewportTransform
})
canvas.on('object:moving', function(e) {
let object = e.target,
objectCenter = object.getCenterPoint(),
transform = canvas._currentTransform
if (!transform) return
isInVerticalCenter = Math.round(objectCenter.x) in canvasWidthCenterMap,
isInHorizontalCenter = Math.round(objectCenter.y) in canvasHeightCenterMap
if (isInHorizontalCenter || isInVerticalCenter) {
object.setPositionByOrigin(new fabric.Point((isInVerticalCenter ? canvasWidthCenter : objectCenter.x), (isInHorizontalCenter ? canvasHeightCenter : objectCenter.y)), 'center', 'center')
}
})
canvas.on('before:render', function() {
canvas.clearContext(canvas.contextTop)
})
canvas.on('after:render', () => {
if (isInVerticalCenter) {
showVerticalCenterLine()
this.centerLine_horizontal = ""
this.centerLine_vertical = (canvasWidthCenter + 0.5) + ", " + 0 + ", " + (canvasWidthCenter + 0.5) + ", " + canvasHeight
}
if (isInHorizontalCenter) {
showHorizontalCenterLine()
this.centerLine_horizontal = (canvasWidthCenter + 0.5) + ", " + 0 + ", " + (canvasWidthCenter + 0.5) + ", " + canvasHeight
this.centerLine_vertical = ""
}
updateInfo()
})
canvas.on('mouse:up', function() {
// clear these values, to stop drawing guidelines once mouse is up
canvas.renderAll()
})
}
// ===============================================
// OBJECT SNAPPING & ALIGNMENT GUIDELINES
// ===============================================
// ORIGINAL:
// https://github.com/fabricjs/fabric.js/blob/master/lib/aligning_guidelines.js
// Original author:
/**
* Should objects be aligned by a bounding box?
* [Bug] Scaled objects sometimes can not be aligned by edges
*
*/
function initAligningGuidelines(canvas) {
let ctx = canvas.getSelectionContext(),
aligningLineOffset = 5,
aligningLineMargin = 4,
aligningLineWidth = 2,
aligningLineColor = 'lime',
viewportTransform,
zoom = null,
verticalLines = [],
horizontalLines = [],
canvasContainer = document.getElementById("myCanvas"),
containerWidth = canvasContainer.offsetWidth,
containerHeight = canvasContainer.offsetHeight
function drawVerticalLine(coords) {
drawLine(
coords.x + 0.5, coords.y1 > coords.y2 ? coords.y2 : coords.y1,
coords.x + 0.5, coords.y2 > coords.y1 ? coords.y2 : coords.y1
)
}
function drawHorizontalLine(coords) {
drawLine(
coords.x1 > coords.x2 ? coords.x2 : coords.x1, coords.y + 0.5,
coords.x2 > coords.x1 ? coords.x2 : coords.x1, coords.y + 0.5
)
}
function drawLine(x1, y1, x2, y2) {
ctx.save()
ctx.lineWidth = aligningLineWidth
ctx.strokeStyle = aligningLineColor
ctx.beginPath()
//console.log("x1 :" + x1)
//console.log("viewportTransform[4] :" + viewportTransform[4])
//console.log("zoom :" + zoom)
ctx.moveTo(
((x1 + viewportTransform[4]) * zoom),
((y1 + viewportTransform[5]) * zoom)
)
//console.log("-------")
//console.log("x1 :" + x1)
//console.log("viewportTransform[4] :" + viewportTransform[4])
//console.log("zoom :" + zoom)
//console.log("x :" + (x1 + canvas.viewportTransform[4]) * zoom)
ctx.lineTo(
((x2 + viewportTransform[4]) * zoom),
((y2 + viewportTransform[5]) * zoom)
)
ctx.stroke()
ctx.restore()
}
function isInRange(value1, value2) {
value1 = Math.round(value1)
value2 = Math.round(value2)
for (var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin; i <= len; i++) {
if (i === value2) {
return true
}
}
return false;
}
canvas.on('mouse:down', function() {
verticalLines.length = horizontalLines.length = 0
viewportTransform = canvas.viewportTransform
zoom = canvas.getZoom()
})
canvas.on('object:moving', (e) => {
verticalLines.length = horizontalLines.length = 0
let activeObject = e.target,
canvasObjects = canvas.getObjects().filter(obj => obj.myType == "box"),
activeObjectCenter = activeObject.getCenterPoint(),
activeObjectLeft = activeObjectCenter.x,
activeObjectTop = activeObjectCenter.y,
activeObjectBoundingRect = activeObject.getBoundingRect(),
activeObjectHeight = activeObjectBoundingRect.height / viewportTransform[3],
activeObjectWidth = activeObjectBoundingRect.width / viewportTransform[0],
horizontalInTheRange = false,
verticalInTheRange = false,
transform = canvas._currentTransform;
//console.log("|||||||||")
//console.log("active acoords is: " + JSON.stringify(activeObject.aCoords, null, 4))
//console.log("active acoords is: " + JSON.stringify(activeObject.oCoords, null, 4))
//console.log("active left offset is: " + JSON.stringify(activeObject.aCoords, null, 4))
//containerWidth = canvasContainer.offsetWidth
//containerHeight = canvasContainer.offsetHeight
//console.log("active left from container is: " + (containerWidth - this.outer.width) / 2 + activeObject.aCoords.tl.x )
if (!transform) return;
// It should be trivial to DRY this up by encapsulating (repeating) creation of x1, x2, y1, and y2 into functions,
// but we're not doing it here for perf. reasons -- as this a function that's invoked on every mouse move
for (let i = canvasObjects.length; i--;) {
if (canvasObjects[i] === activeObject) continue
let objectCenter = canvasObjects[i].getCenterPoint(),
objectLeft = objectCenter.x,
objectTop = objectCenter.y,
objectBoundingRect = canvasObjects[i].getBoundingRect(),
objectHeight = objectBoundingRect.height / viewportTransform[3],
objectWidth = objectBoundingRect.width / viewportTransform[0]
// snap by the horizontal center line
if (isInRange(objectLeft, activeObjectLeft)) {
verticalInTheRange = true
verticalLines.push({
x: objectLeft,
y1: (objectTop < activeObjectTop) ?
(objectTop - objectHeight / 2 - aligningLineOffset) :
(objectTop + objectHeight / 2 + aligningLineOffset),
y2: (activeObjectTop > objectTop) ?
(activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) :
(activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(objectLeft, activeObjectTop), 'center', 'center');
}
// snap by the left edge
if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) {
verticalInTheRange = true
verticalLines.push({
x: objectLeft - objectWidth / 2,
y1: (objectTop < activeObjectTop) ?
(objectTop - objectHeight / 2 - aligningLineOffset) :
(objectTop + objectHeight / 2 + aligningLineOffset),
y2: (activeObjectTop > objectTop) ?
(activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) :
(activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(objectLeft - objectWidth / 2 + activeObjectWidth / 2, activeObjectTop), 'center', 'center')
}
// snap by the right edge
if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) {
verticalInTheRange = true
verticalLines.push({
x: objectLeft + objectWidth / 2,
y1: (objectTop < activeObjectTop) ?
(objectTop - objectHeight / 2 - aligningLineOffset) :
(objectTop + objectHeight / 2 + aligningLineOffset),
y2: (activeObjectTop > objectTop) ?
(activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) :
(activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(objectLeft + objectWidth / 2 - activeObjectWidth / 2, activeObjectTop), 'center', 'center')
}
// snap by the vertical center line
if (isInRange(objectTop, activeObjectTop)) {
horizontalInTheRange = true;
horizontalLines.push({
y: objectTop,
x1: (objectLeft < activeObjectLeft) ?
(objectLeft - objectWidth / 2 - aligningLineOffset) :
(objectLeft + objectWidth / 2 + aligningLineOffset),
x2: (activeObjectLeft > objectLeft) ?
(activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) :
(activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop), 'center', 'center')
}
// snap by the top edge
if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
horizontalInTheRange = true
horizontalLines.push({
y: objectTop - objectHeight / 2,
x1: (objectLeft < activeObjectLeft) ?
(objectLeft - objectWidth / 2 - aligningLineOffset) :
(objectLeft + objectWidth / 2 + aligningLineOffset),
x2: (activeObjectLeft > objectLeft) ?
(activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) :
(activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 + activeObjectHeight / 2), 'center', 'center');
}
// snap by the bottom edge
if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
horizontalInTheRange = true
horizontalLines.push({
y: objectTop + objectHeight / 2,
x1: (objectLeft < activeObjectLeft) ?
(objectLeft - objectWidth / 2 - aligningLineOffset) :
(objectLeft + objectWidth / 2 + aligningLineOffset),
x2: (activeObjectLeft > objectLeft) ?
(activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) :
(activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 - activeObjectHeight / 2), 'center', 'center')
}
}
if (!horizontalInTheRange) {
horizontalLines.length = 0
}
if (!verticalInTheRange) {
verticalLines.length = 0
}
})
canvas.on('mouse:wheel', (opt) => {
verticalLines.length = horizontalLines.length = 0
})
canvas.on('before:render', function() {
canvas.clearContext(canvas.contextTop)
})
canvas.on('after:render', () => {
for (let i = verticalLines.length; i--;) {
drawVerticalLine(verticalLines[i])
}
for (let i = horizontalLines.length; i--;) {
drawHorizontalLine(horizontalLines[i])
}
this.alignmentLines_horizontal = JSON.stringify(horizontalLines, null, 4)
this.alignmentLines_vertical = JSON.stringify(verticalLines, null, 4)
updateInfo()
// console.log("activeObject left edge x is: " + canvas.getActiveObject().left)
//verticalLines.length = horizontalLines.length = 0
canvas.calcOffset()
})
canvas.on('mouse:up', () => {
//verticalLines.length = horizontalLines.length = 0
canvas.renderAll()
//this.alignmentLines_horizontal = horizontalLines
//this.alignmentLines_vertical = verticalLines
//updateInfo()
})
}
#container {
display: flex;
font-family: sans-serif;
}
#header {
display: flex;
}
#reset {
background-color: #333333;
color: #ffffff;
padding: 1em;
border: none;
margin: 0.5em;
margin-top: 6em;
cursor: pointer;
}
#reset:hover {
background-color: #666666;
}
#reset:active {
background-color: #333333;
}
#info {
/* display: flex; */
display: none;
flex-direction: column;
}
#info>div {
display: flex;
flex-direction: column;
}
#info>div>div {
display: flex;
margin: 0.5em;
}
canvas {
display: block;
}
hr {
width: 100%;
}
<script src="https://pagecdn.io/lib/fabric/3.6.3/fabric.min.js"></script>
<div id="container">
<canvas id="myCanvas" width="500" height="300"></canvas>
<div id="sidebar">
<button id="reset">RESET</button>
<div id="info">
<div>
<div><b>zoom:</b>
<div id="info_zoom"></div>
</div>
<div><b>viewport top:</b>
<div id="info_vptTop"></div>
</div>
<div><b>viewport left:</b>
<div id="info_vptLeft"></div>
</div>
</div>
<hr />
<div>
<div><b>Alignment lines (green)</b></div>
<div><b>Horizontal:</b>
<div id="info_alignmentLines_horizontal"></div>
</div>
<div><b>Vertical:</b>
<div id="info_alignmentLines_vertical"></div>
</div>
</div>
<hr />
<div>
<div><b>Canvas-center lines (purple)</b></div>
<div><b>Horizontal:</b>
<div id="info_centerLine_horizontal"></div>
</div>
<div><b>Vertical:</b>
<div id="info_centerLine_vertical"></div>
</div>
</div>
</div>
</div>
</div>
我换了drawLine
function.Should工作
https://jsfiddle.net/3mtcsy6p/1/
function drawLine(x1, y1, x2, y2) {
var originXY = fabric.util.transformPoint(new fabric.Point(x1, y1), canvas.viewportTransform),
dimensions = fabric.util.transformPoint(new fabric.Point(x2, y2),canvas.viewportTransform);
ctx.save()
ctx.lineWidth = aligningLineWidth
ctx.strokeStyle = aligningLineColor
ctx.beginPath()
ctx.moveTo(
( (originXY.x ) ),
( (originXY.y ) )
)
ctx.lineTo(
( (dimensions.x ) ),
( (dimensions.y ) )
)
ctx.stroke()
ctx.restore()
}
对于我的 fabric.js 项目:我正在尝试设置对象捕捉和对齐准则。对于捕捉,这意味着当用户四处拖动对象时,如果对象的任何边缘接近与另一个对象边缘对齐,它将捕捉到位。在此期间,指南会作为用户的视觉助手出现。
到目前为止,我正在实施现有的工作,这些工作由各种 fabric.js 贡献者完成,可在此处找到:
centering_guidelines.js & aligning_guidelines.js.
作品:在默认缩放 (1) 下,对象捕捉和对齐准则效果很好!
失败:放大(放大或缩小)时,视觉指南出现在错误的位置,但捕捉保持正确的功能。
代码示例:四处移动对象。在默认缩放下,捕捉和指南效果很好。更改缩放级别(使用鼠标滚轮)并注意指南未正确定位,但捕捉工作正常。
示例 1:简单
原始库按原样加载;简单的演示。
https://codepen.io/MarsAndBack/pen/ZEQMXoM
示例 2:详细
内联复制粘贴原始库,并进行修改以帮助调查。
https://codepen.io/MarsAndBack/pen/LYGJGoq
注意:Codepen 有完整的代码。
// ==========================================
// SETUP
// ==========================================
const canvas = new fabric.Canvas("myCanvas")
canvas.backgroundColor = "#222222";
var lastClientX = 0
var lastClientY = 0
var state = "default"
const outer = null
const box1 = null
const box2 = null
this.centerLine_horizontal = ""
this.centerLine_vertical = ""
this.alignmentLines_horizontal = ""
this.alignmentLines_vertical = ""
fabric.Object.prototype.set({
cornerSize: 15,
cornerStyle: 'circle',
cornerColor: '#ffffff',
transparentCorners: false
})
setupObjects()
updateInfo(canvas)
function setupObjects() {
this.outer = new fabric.Rect({
width: canvas.getWidth(),
height: canvas.getHeight(),
top: 20,
left: 20,
stroke: '#ffffff',
evented: false,
selectable: false
})
this.box1 = new fabric.Rect({
width: 240,
height: 100,
top: 20,
left: 20,
fill: '#fff28a',
myType: "box"
})
this.box2 = new fabric.Rect({
width: 240,
height: 100,
top: 140,
left: 20,
fill: '#ff8a8a',
myType: "box"
})
this.box3 = new fabric.Rect({
width: 100,
height: 160,
top: 20,
left: 280,
fill: '#cf8aff',
myType: "box"
})
canvas.add(this.outer)
this.outer.center()
canvas.add(this.box1)
canvas.add(this.box2)
canvas.add(this.box3)
let allBoxes = new fabric.ActiveSelection(canvas.getObjects().filter(obj => obj.myType == "box"), {
canvas: canvas
})
allBoxes.center()
allBoxes.destroy()
}
function updateInfo() {
let info_zoom = document.getElementById('info_zoom')
let info_vptTop = document.getElementById('info_vptTop')
let info_vptLeft = document.getElementById('info_vptLeft')
let info_centerLine_horizontal = document.getElementById('info_centerLine_horizontal')
let info_centerLine_vertical = document.getElementById('info_centerLine_vertical')
let info_alignmentLines_horizontal = document.getElementById('info_alignmentLines_horizontal')
let info_alignmentLines_vertical = document.getElementById('info_alignmentLines_vertical')
info_zoom.innerHTML = canvas.getZoom().toFixed(2)
info_vptTop.innerHTML = Math.round(canvas.viewportTransform[5])
info_vptLeft.innerHTML = Math.round(canvas.viewportTransform[4])
info_centerLine_horizontal.innerHTML = this.centerLine_horizontal
info_centerLine_vertical.innerHTML = this.centerLine_vertical
info_alignmentLines_horizontal.innerHTML = this.alignmentLines_horizontal
info_alignmentLines_vertical.innerHTML = this.alignmentLines_vertical
}
// ------------------------------------
// Reset
// ------------------------------------
let resetButton = document.getElementById('reset')
resetButton.addEventListener('click', function() {
reset()
}, false)
function reset() {
canvas.remove(...canvas.getObjects())
setupObjects()
canvas.setViewportTransform([1, 0, 0, 1, 0, 0])
updateInfo()
}
// ------------------------------------
// ==========================================
// MOUSE INTERACTIONS
// ==========================================
// MOUSEWHEEL ZOOM
canvas.on('mouse:wheel', (opt) => {
let delta = 0
// -------------------------------
// WHEEL RESOLUTION
let wheelDelta = opt.e.wheelDelta
let deltaY = opt.e.deltaY
// CHROME WIN/MAC | SAFARI 7 MAC | OPERA WIN/MAC | EDGE
if (wheelDelta) {
delta = -wheelDelta / 120
}
// FIREFOX WIN / MAC | IE
if (deltaY) {
deltaY > 0 ? delta = 1 : delta = -1
}
// -------------------------------
let pointer = canvas.getPointer(opt.e)
let zoom = canvas.getZoom()
zoom = zoom - delta / 10
// limit zoom in
if (zoom > 4) zoom = 4
// limit zoom out
if (zoom < 0.2) {
zoom = 0.2
}
//canvas.zoomToPoint({
// x: opt.e.offsetX,
// y: opt.e.offsetY
//}, zoom)
canvas.zoomToPoint(
new fabric.Point(canvas.width / 2, canvas.height / 2),
zoom);
opt.e.preventDefault()
opt.e.stopPropagation()
canvas.renderAll()
canvas.calcOffset()
updateInfo(canvas)
})
initCenteringGuidelines(canvas)
initAligningGuidelines(canvas)
// ==========================================
// CANVAS CENTER SNAPPING & ALIGNMENT GUIDELINES
// ==========================================
// ORIGINAL:
// https://github.com/fabricjs/fabric.js/blob/master/lib/centering_guidelines.js
/**
* Augments canvas by assigning to `onObjectMove` and `onAfterRender`.
* This kind of sucks because other code using those methods will stop functioning.
* Need to fix it by replacing callbacks with pub/sub kind of subscription model.
* (or maybe use existing fabric.util.fire/observe (if it won't be too slow))
*/
function initCenteringGuidelines(canvas) {
let canvasWidth = canvas.getWidth(),
canvasHeight = canvas.getHeight(),
canvasWidthCenter = canvasWidth / 2,
canvasHeightCenter = canvasHeight / 2,
canvasWidthCenterMap = {},
canvasHeightCenterMap = {},
centerLineMargin = 4,
centerLineColor = 'purple',
centerLineWidth = 2,
ctx = canvas.getSelectionContext(),
viewportTransform
for (let i = canvasWidthCenter - centerLineMargin, len = canvasWidthCenter + centerLineMargin; i <= len; i++) {
canvasWidthCenterMap[Math.round(i)] = true
}
for (let i = canvasHeightCenter - centerLineMargin, len = canvasHeightCenter + centerLineMargin; i <= len; i++) {
canvasHeightCenterMap[Math.round(i)] = true
}
function showVerticalCenterLine() {
showCenterLine(canvasWidthCenter + 0.5, 0, canvasWidthCenter + 0.5, canvasHeight)
}
function showHorizontalCenterLine() {
showCenterLine(0, canvasHeightCenter + 0.5, canvasWidth, canvasHeightCenter + 0.5)
}
function showCenterLine(x1, y1, x2, y2) {
ctx.save()
ctx.strokeStyle = centerLineColor
ctx.lineWidth = centerLineWidth
ctx.beginPath()
ctx.moveTo(x1 * viewportTransform[0], y1 * viewportTransform[3])
ctx.lineTo(x2 * viewportTransform[0], y2 * viewportTransform[3])
ctx.stroke()
ctx.restore()
}
let afterRenderActions = [],
isInVerticalCenter,
isInHorizontalCenter
canvas.on('mouse:down', () => {
isInVerticalCenter = isInHorizontalCenter = null
this.centerLine_horizontal = ""
this.centerLine_vertical = ""
updateInfo()
viewportTransform = canvas.viewportTransform
})
canvas.on('object:moving', function(e) {
let object = e.target,
objectCenter = object.getCenterPoint(),
transform = canvas._currentTransform
if (!transform) return
isInVerticalCenter = Math.round(objectCenter.x) in canvasWidthCenterMap,
isInHorizontalCenter = Math.round(objectCenter.y) in canvasHeightCenterMap
if (isInHorizontalCenter || isInVerticalCenter) {
object.setPositionByOrigin(new fabric.Point((isInVerticalCenter ? canvasWidthCenter : objectCenter.x), (isInHorizontalCenter ? canvasHeightCenter : objectCenter.y)), 'center', 'center')
}
})
canvas.on('before:render', function() {
canvas.clearContext(canvas.contextTop)
})
canvas.on('after:render', () => {
if (isInVerticalCenter) {
showVerticalCenterLine()
this.centerLine_horizontal = ""
this.centerLine_vertical = (canvasWidthCenter + 0.5) + ", " + 0 + ", " + (canvasWidthCenter + 0.5) + ", " + canvasHeight
}
if (isInHorizontalCenter) {
showHorizontalCenterLine()
this.centerLine_horizontal = (canvasWidthCenter + 0.5) + ", " + 0 + ", " + (canvasWidthCenter + 0.5) + ", " + canvasHeight
this.centerLine_vertical = ""
}
updateInfo()
})
canvas.on('mouse:up', function() {
// clear these values, to stop drawing guidelines once mouse is up
canvas.renderAll()
})
}
// ===============================================
// OBJECT SNAPPING & ALIGNMENT GUIDELINES
// ===============================================
// ORIGINAL:
// https://github.com/fabricjs/fabric.js/blob/master/lib/aligning_guidelines.js
// Original author:
/**
* Should objects be aligned by a bounding box?
* [Bug] Scaled objects sometimes can not be aligned by edges
*
*/
function initAligningGuidelines(canvas) {
let ctx = canvas.getSelectionContext(),
aligningLineOffset = 5,
aligningLineMargin = 4,
aligningLineWidth = 2,
aligningLineColor = 'lime',
viewportTransform,
zoom = null,
verticalLines = [],
horizontalLines = [],
canvasContainer = document.getElementById("myCanvas"),
containerWidth = canvasContainer.offsetWidth,
containerHeight = canvasContainer.offsetHeight
function drawVerticalLine(coords) {
drawLine(
coords.x + 0.5, coords.y1 > coords.y2 ? coords.y2 : coords.y1,
coords.x + 0.5, coords.y2 > coords.y1 ? coords.y2 : coords.y1
)
}
function drawHorizontalLine(coords) {
drawLine(
coords.x1 > coords.x2 ? coords.x2 : coords.x1, coords.y + 0.5,
coords.x2 > coords.x1 ? coords.x2 : coords.x1, coords.y + 0.5
)
}
function drawLine(x1, y1, x2, y2) {
ctx.save()
ctx.lineWidth = aligningLineWidth
ctx.strokeStyle = aligningLineColor
ctx.beginPath()
//console.log("x1 :" + x1)
//console.log("viewportTransform[4] :" + viewportTransform[4])
//console.log("zoom :" + zoom)
ctx.moveTo(
((x1 + viewportTransform[4]) * zoom),
((y1 + viewportTransform[5]) * zoom)
)
//console.log("-------")
//console.log("x1 :" + x1)
//console.log("viewportTransform[4] :" + viewportTransform[4])
//console.log("zoom :" + zoom)
//console.log("x :" + (x1 + canvas.viewportTransform[4]) * zoom)
ctx.lineTo(
((x2 + viewportTransform[4]) * zoom),
((y2 + viewportTransform[5]) * zoom)
)
ctx.stroke()
ctx.restore()
}
function isInRange(value1, value2) {
value1 = Math.round(value1)
value2 = Math.round(value2)
for (var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin; i <= len; i++) {
if (i === value2) {
return true
}
}
return false;
}
canvas.on('mouse:down', function() {
verticalLines.length = horizontalLines.length = 0
viewportTransform = canvas.viewportTransform
zoom = canvas.getZoom()
})
canvas.on('object:moving', (e) => {
verticalLines.length = horizontalLines.length = 0
let activeObject = e.target,
canvasObjects = canvas.getObjects().filter(obj => obj.myType == "box"),
activeObjectCenter = activeObject.getCenterPoint(),
activeObjectLeft = activeObjectCenter.x,
activeObjectTop = activeObjectCenter.y,
activeObjectBoundingRect = activeObject.getBoundingRect(),
activeObjectHeight = activeObjectBoundingRect.height / viewportTransform[3],
activeObjectWidth = activeObjectBoundingRect.width / viewportTransform[0],
horizontalInTheRange = false,
verticalInTheRange = false,
transform = canvas._currentTransform;
//console.log("|||||||||")
//console.log("active acoords is: " + JSON.stringify(activeObject.aCoords, null, 4))
//console.log("active acoords is: " + JSON.stringify(activeObject.oCoords, null, 4))
//console.log("active left offset is: " + JSON.stringify(activeObject.aCoords, null, 4))
//containerWidth = canvasContainer.offsetWidth
//containerHeight = canvasContainer.offsetHeight
//console.log("active left from container is: " + (containerWidth - this.outer.width) / 2 + activeObject.aCoords.tl.x )
if (!transform) return;
// It should be trivial to DRY this up by encapsulating (repeating) creation of x1, x2, y1, and y2 into functions,
// but we're not doing it here for perf. reasons -- as this a function that's invoked on every mouse move
for (let i = canvasObjects.length; i--;) {
if (canvasObjects[i] === activeObject) continue
let objectCenter = canvasObjects[i].getCenterPoint(),
objectLeft = objectCenter.x,
objectTop = objectCenter.y,
objectBoundingRect = canvasObjects[i].getBoundingRect(),
objectHeight = objectBoundingRect.height / viewportTransform[3],
objectWidth = objectBoundingRect.width / viewportTransform[0]
// snap by the horizontal center line
if (isInRange(objectLeft, activeObjectLeft)) {
verticalInTheRange = true
verticalLines.push({
x: objectLeft,
y1: (objectTop < activeObjectTop) ?
(objectTop - objectHeight / 2 - aligningLineOffset) :
(objectTop + objectHeight / 2 + aligningLineOffset),
y2: (activeObjectTop > objectTop) ?
(activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) :
(activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(objectLeft, activeObjectTop), 'center', 'center');
}
// snap by the left edge
if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) {
verticalInTheRange = true
verticalLines.push({
x: objectLeft - objectWidth / 2,
y1: (objectTop < activeObjectTop) ?
(objectTop - objectHeight / 2 - aligningLineOffset) :
(objectTop + objectHeight / 2 + aligningLineOffset),
y2: (activeObjectTop > objectTop) ?
(activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) :
(activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(objectLeft - objectWidth / 2 + activeObjectWidth / 2, activeObjectTop), 'center', 'center')
}
// snap by the right edge
if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) {
verticalInTheRange = true
verticalLines.push({
x: objectLeft + objectWidth / 2,
y1: (objectTop < activeObjectTop) ?
(objectTop - objectHeight / 2 - aligningLineOffset) :
(objectTop + objectHeight / 2 + aligningLineOffset),
y2: (activeObjectTop > objectTop) ?
(activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) :
(activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(objectLeft + objectWidth / 2 - activeObjectWidth / 2, activeObjectTop), 'center', 'center')
}
// snap by the vertical center line
if (isInRange(objectTop, activeObjectTop)) {
horizontalInTheRange = true;
horizontalLines.push({
y: objectTop,
x1: (objectLeft < activeObjectLeft) ?
(objectLeft - objectWidth / 2 - aligningLineOffset) :
(objectLeft + objectWidth / 2 + aligningLineOffset),
x2: (activeObjectLeft > objectLeft) ?
(activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) :
(activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop), 'center', 'center')
}
// snap by the top edge
if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
horizontalInTheRange = true
horizontalLines.push({
y: objectTop - objectHeight / 2,
x1: (objectLeft < activeObjectLeft) ?
(objectLeft - objectWidth / 2 - aligningLineOffset) :
(objectLeft + objectWidth / 2 + aligningLineOffset),
x2: (activeObjectLeft > objectLeft) ?
(activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) :
(activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 + activeObjectHeight / 2), 'center', 'center');
}
// snap by the bottom edge
if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
horizontalInTheRange = true
horizontalLines.push({
y: objectTop + objectHeight / 2,
x1: (objectLeft < activeObjectLeft) ?
(objectLeft - objectWidth / 2 - aligningLineOffset) :
(objectLeft + objectWidth / 2 + aligningLineOffset),
x2: (activeObjectLeft > objectLeft) ?
(activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) :
(activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 - activeObjectHeight / 2), 'center', 'center')
}
}
if (!horizontalInTheRange) {
horizontalLines.length = 0
}
if (!verticalInTheRange) {
verticalLines.length = 0
}
})
canvas.on('mouse:wheel', (opt) => {
verticalLines.length = horizontalLines.length = 0
})
canvas.on('before:render', function() {
canvas.clearContext(canvas.contextTop)
})
canvas.on('after:render', () => {
for (let i = verticalLines.length; i--;) {
drawVerticalLine(verticalLines[i])
}
for (let i = horizontalLines.length; i--;) {
drawHorizontalLine(horizontalLines[i])
}
this.alignmentLines_horizontal = JSON.stringify(horizontalLines, null, 4)
this.alignmentLines_vertical = JSON.stringify(verticalLines, null, 4)
updateInfo()
// console.log("activeObject left edge x is: " + canvas.getActiveObject().left)
//verticalLines.length = horizontalLines.length = 0
canvas.calcOffset()
})
canvas.on('mouse:up', () => {
//verticalLines.length = horizontalLines.length = 0
canvas.renderAll()
//this.alignmentLines_horizontal = horizontalLines
//this.alignmentLines_vertical = verticalLines
//updateInfo()
})
}
#container {
display: flex;
font-family: sans-serif;
}
#header {
display: flex;
}
#reset {
background-color: #333333;
color: #ffffff;
padding: 1em;
border: none;
margin: 0.5em;
margin-top: 6em;
cursor: pointer;
}
#reset:hover {
background-color: #666666;
}
#reset:active {
background-color: #333333;
}
#info {
/* display: flex; */
display: none;
flex-direction: column;
}
#info>div {
display: flex;
flex-direction: column;
}
#info>div>div {
display: flex;
margin: 0.5em;
}
canvas {
display: block;
}
hr {
width: 100%;
}
<script src="https://pagecdn.io/lib/fabric/3.6.3/fabric.min.js"></script>
<div id="container">
<canvas id="myCanvas" width="500" height="300"></canvas>
<div id="sidebar">
<button id="reset">RESET</button>
<div id="info">
<div>
<div><b>zoom:</b>
<div id="info_zoom"></div>
</div>
<div><b>viewport top:</b>
<div id="info_vptTop"></div>
</div>
<div><b>viewport left:</b>
<div id="info_vptLeft"></div>
</div>
</div>
<hr />
<div>
<div><b>Alignment lines (green)</b></div>
<div><b>Horizontal:</b>
<div id="info_alignmentLines_horizontal"></div>
</div>
<div><b>Vertical:</b>
<div id="info_alignmentLines_vertical"></div>
</div>
</div>
<hr />
<div>
<div><b>Canvas-center lines (purple)</b></div>
<div><b>Horizontal:</b>
<div id="info_centerLine_horizontal"></div>
</div>
<div><b>Vertical:</b>
<div id="info_centerLine_vertical"></div>
</div>
</div>
</div>
</div>
</div>
我换了drawLine
function.Should工作
https://jsfiddle.net/3mtcsy6p/1/
function drawLine(x1, y1, x2, y2) {
var originXY = fabric.util.transformPoint(new fabric.Point(x1, y1), canvas.viewportTransform),
dimensions = fabric.util.transformPoint(new fabric.Point(x2, y2),canvas.viewportTransform);
ctx.save()
ctx.lineWidth = aligningLineWidth
ctx.strokeStyle = aligningLineColor
ctx.beginPath()
ctx.moveTo(
( (originXY.x ) ),
( (originXY.y ) )
)
ctx.lineTo(
( (dimensions.x ) ),
( (dimensions.y ) )
)
ctx.stroke()
ctx.restore()
}