Как реализовать панорамирование холста с помощью Fabric.js
У меня есть холст Fabric.js, и я хочу реализовать полномасштабное панорамирование, которое пакеты программного обеспечения обычно выполняют с помощью "ручного" инструмента. Это когда вы нажимаете одну из кнопок мыши, затем перемещайтесь по холсту, удерживая кнопку мыши, и видимая часть холста изменяется соответственно.
Вы можете видеть в этом видео, чего я хочу достичь.
Чтобы реализовать эту функциональность, я написал следующий код:
$(canvas.wrapperEl).on('mousemove', function(evt) {
if (evt.button == 2) { // 2 is the right mouse button
canvas.absolutePan({
x: evt.clientX,
y: evt.clientY
});
}
});
Но это не сработает. Вы можете видеть в этом видео, что происходит.
Как изменить код в следующем порядке:
-
Для панорамирования для работы, как в первом видео?
-
Чтобы обработчик события использовал событие? Он должен помешать появлению контекстного меню, когда пользователь нажимает или отпускает правую кнопку мыши.
Ответы
Ответ 1
Легкий способ панорамирования холста Fabric в ответ на движение мыши - это вычислить смещение курсора между событиями мыши и передать его на relativePan
.
Обратите внимание, как мы можем использовать свойства screenX
и screenY
предыдущего события мыши для вычисления относительного положения текущего события мыши:
function startPan(event) {
if (event.button != 2) {
return;
}
var x0 = event.screenX,
y0 = event.screenY;
function continuePan(event) {
var x = event.screenX,
y = event.screenY;
fc.relativePan({ x: x - x0, y: y - y0 });
x0 = x;
y0 = y;
}
function stopPan(event) {
$(window).off('mousemove', continuePan);
$(window).off('mouseup', stopPan);
};
$(window).mousemove(continuePan);
$(window).mouseup(stopPan);
$(window).contextmenu(cancelMenu);
};
function cancelMenu() {
$(window).off('contextmenu', cancelMenu);
return false;
}
$(canvasWrapper).mousedown(startPan);
Мы начинаем панорамирование на mousedown
и продолжаем панорамирование на mousemove
. На mouseup
мы отменим панорамирование; мы также отменяем функцию mouseup
-cancelling.
Меню правой кнопки мыши, также известное как контекстное меню, отменяется возвратом false
. Функция отмены меню также отменяет себя. Таким образом, контекстное меню будет работать, если вы впоследствии щелкните за пределами оболочки холста.
Вот страница, демонстрирующая этот подход:
http://michaellaszlo.com/so/fabric-pan/
Вы увидите три изображения на холсте ткани (для загрузки изображений может потребоваться время или два). Вы сможете использовать стандартную функциональность Fabric. Вы можете щелкнуть левой кнопкой мыши по изображениям, чтобы перемещать их, растягивать и вращать. Но когда вы щелкните правой кнопкой мыши в контейнере canvas, вы перемещаете весь холст Ткань с помощью мыши.
Ответ 2
Не уверен в FabricJS, но он может быть таким:
-
чтобы он работал как в первом видео:
Используя свойство CSS cursor
, переключая его на события mousedown
и mouseup
, используя javascript.
-
обработчик события потребляет событие (предотвращение появления контекстного меню, когда пользователь отпускает правую кнопку мыши):
Используя javascript, мы возвращаем false
в contextmenu
событие
КОД: с небольшой проблемой (*)
с помощью jQuery JS Fiddle 1
$('#test').on('mousedown', function(e){
if (e.button == 2) {
// if right-click, set cursor shape to grabbing
$(this).css({'cursor':'grabbing'});
}
}).on('mouseup', function(){
// set cursor shape to default
$(this).css({'cursor':'default'});
}).on('contextmenu', function(){
//disable context menu on right click
return false;
});
Использование raw javascript JS Fiddle 2
var test = document.getElementById('test');
test.addEventListener('mousedown', function(e){
if (e.button == 2) {
// if right-click, set cursor shape to grabbing
this.style.cursor = 'grabbing';
}
});
test.addEventListener('mouseup', function(){
// set cursor shape to default
this.style.cursor = 'default';
});
test.oncontextmenu = function(){
//disable context menu on right click
return false;
}
Ответ 3
Я сделал пример на jsfiddle, что мы можем фактически перетащить весь холст со всеми его объектами в родительский div, как и изображение, и я попытаюсь объяснить его шаг за шагом.
![введите описание изображения здесь]()
-
Прежде всего, я загружаю библиотеку перетаскивания jquery.dradscroll.js, вы можете найти ее в сети. Это небольшой файл js, который с небольшими изменениями может помочь нам выполнить задачу.
ссылка для скачивания: http://www.java2s.com/Open-Source/Javascript_Free_Code/jQuery_Scroll/Download_jquery_dragscroll_Free_Java_Code.htm
-
создайте контейнер, содержащий наш холст.
<div class="content">
<canvas id="c" width="600" height="700" ></canvas>
</div>
-
немного css
.content{
overflow:auto;
width:400px;
height:400px;
}
-
JavaScript:
а. создайте холст.
б. сделать по умолчанию курсор, когда он находится над холстом, открыть руку
canvas.defaultCursor = 'url(" http://maps.gstatic.com/intl/en_us/mapfiles/openhand_8_8.cur") 15 15, crosshair';
с. переопределить функцию __onMouseDown, для перехода на закрытый курсор (в конце).
fabric.Canvas.prototype.__onMouseDown = function(e){
// accept only left clicks
var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1;
if (!isLeftClick && !fabric.isTouchSupported) {
return;
}
if (this.isDrawingMode) {
this._onMouseDownInDrawingMode(e);
return;
}
// ignore if some object is being transformed at this moment
if (this._currentTransform) {
return;
}
var target = this.findTarget(e),
pointer = this.getPointer(e, true);
// save pointer for check in __onMouseUp event
this._previousPointer = pointer;
var shouldRender = this._shouldRender(target, pointer),
shouldGroup = this._shouldGroup(e, target);
if (this._shouldClearSelection(e, target)) {
this._clearSelection(e, target, pointer);
}
else if (shouldGroup) {
this._handleGrouping(e, target);
target = this.getActiveGroup();
}
if (target && target.selectable && !shouldGroup) {
this._beforeTransform(e, target);
this._setupCurrentTransform(e, target);
}
// we must renderAll so that active image is placed on the top canvas
shouldRender && this.renderAll();
this.fire('mouse:down', { target: target, e: e });
target && target.fire('mousedown', { e: e });
if(!canvas.getActiveObject() || !canvas.getActiveGroup()){
flag=true;
//change cursor to closedhand.cur
canvas.defaultCursor = 'url("http://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur") 15 15, crosshair';
}//end if
-
переопределить событие __onMouseUp, чтобы изменить курсор на открытую.
fabric.Canvas.prototype.__onMouseUp = function(e){
if(flag){
canvas.defaultCursor = 'url(" http://maps.gstatic.com/intl/en_us/mapfiles/openhand_8_8.cur") 15 15, crosshair';
flag=false;
}
};
-
Вы инициализируете dragScroll() для работы с содержимым, на котором размещается холст:
$('.content').dragScroll({});
-
Небольшие изменения в файле jquery.dragScroll.js, чтобы понять, когда нужно перетаскивать холст, а не. В событии mousedown() мы добавляем оператор if, чтобы проверить, есть ли у нас активный объект или группа. Если это правда, перетяните холст.
$($scrollArea).mousedown(function (e) {
if (canvas.getActiveObject() || canvas.getActiveGroup()) {
console.log('no drag');return;
} else {
console.log($('body'));
if (typeof options.limitTo == "object") {
for (var i = 0; i < options.limitTo.length; i++) {
if ($(e.target).hasClass(options.limitTo[i])) {
doMousedown(e);
}
}
} else {
doMousedown(e);
}
}
});
-
в событии mousedown мы захватываем элемент DOM (.content) и получаем верхнюю и левую позицию
function doMousedown(e) {
e.preventDefault();
down = true;
x = e.pageX;
y = e.pageY;
top = e.target.parentElement.parentElement.scrollTop; // .content
left = e.target.parentElement.parentElement.scrollLeft;// .content
}
-
Если мы не хотим видеть полосы прокрутки:
.content{
overflow:hidden;
width:400px;
height:400px;
}
-
Есть небольшая проблема, хотя jsfiddle принимает только библиотеки https, поэтому она блокирует fabricjs, за исключением того, что вы добавляете ее из https://rawgit.com/kangax/fabric.js/master/dist/fabric.js ', но снова он по-прежнему блокирует его несколько раз (по крайней мере, на моем хроме и мозилле).
Пример jsfiddle: https://jsfiddle.net/tornado1979/up48rxLs/
вам может быть повезло больше, чем мне, в вашем браузере, но это определенно будет работать над вашим живым приложением.
В любом случае, надеюсь, поможет, удачи.
Ответ 4
У меня есть пример в Github с использованием fabric.js Canvas panning: https://sabatinomasala.github.io/fabric-clipping-demo/
Код, ответственный за поведение панорамирования, следующий: https://github.com/SabatinoMasala/fabric-clipping-demo/blob/master/src/classes/Panning.js
Это простое расширение на fabric.Canvas.prototype
, которое позволяет вам переключать режим перетаскивания на холст следующим образом:
canvas.toggleDragMode(true); // Start panning
canvas.toggleDragMode(false); // Stop panning
Взгляните на следующий фрагмент, документация доступна по всему коду.
const STATE_IDLE = 'idle';
const STATE_PANNING = 'panning';
fabric.Canvas.prototype.toggleDragMode = function(dragMode) {
// Remember the previous X and Y coordinates for delta calculations
let lastClientX;
let lastClientY;
// Keep track of the state
let state = STATE_IDLE;
// We're entering dragmode
if (dragMode) {
// Discard any active object
this.discardActiveObject();
// Set the cursor to 'move'
this.defaultCursor = 'move';
// Loop over all objects and disable events / selectable. We remember its value in a temp variable stored on each object
this.forEachObject(function(object) {
object.prevEvented = object.evented;
object.prevSelectable = object.selectable;
object.evented = false;
object.selectable = false;
});
// Remove selection ability on the canvas
this.selection = false;
// When MouseUp fires, we set the state to idle
this.on('mouse:up', function(e) {
state = STATE_IDLE;
});
// When MouseDown fires, we set the state to panning
this.on('mouse:down', (e) => {
state = STATE_PANNING;
lastClientX = e.e.clientX;
lastClientY = e.e.clientY;
});
// When the mouse moves, and we're panning (mouse down), we continue
this.on('mouse:move', (e) => {
if (state === STATE_PANNING && e && e.e) {
// let delta = new fabric.Point(e.e.movementX, e.e.movementY); // No Safari support for movementX and movementY
// For cross-browser compatibility, I had to manually keep track of the delta
// Calculate deltas
let deltaX = 0;
let deltaY = 0;
if (lastClientX) {
deltaX = e.e.clientX - lastClientX;
}
if (lastClientY) {
deltaY = e.e.clientY - lastClientY;
}
// Update the last X and Y values
lastClientX = e.e.clientX;
lastClientY = e.e.clientY;
let delta = new fabric.Point(deltaX, deltaY);
this.relativePan(delta);
this.trigger('moved');
}
});
} else {
// When we exit dragmode, we restore the previous values on all objects
this.forEachObject(function(object) {
object.evented = (object.prevEvented !== undefined) ? object.prevEvented : object.evented;
object.selectable = (object.prevSelectable !== undefined) ? object.prevSelectable : object.selectable;
});
// Reset the cursor
this.defaultCursor = 'default';
// Remove the event listeners
this.off('mouse:up');
this.off('mouse:down');
this.off('mouse:move');
// Restore selection ability on the canvas
this.selection = true;
}
};
// Create the canvas
let canvas = new fabric.Canvas('fabric')
canvas.backgroundColor = '#f1f1f1';
// Add a couple of rects
let rect = new fabric.Rect({
width: 100,
height: 100,
fill: '#f00'
});
canvas.add(rect)
rect = new fabric.Rect({
width: 200,
height: 200,
top: 200,
left: 200,
fill: '#f00'
});
canvas.add(rect)
// Handle dragmode change
let dragMode = false;
$('#dragmode').change(_ => {
dragMode = !dragMode;
canvas.toggleDragMode(dragMode);
});
<div>
<label for="dragmode">
Enable panning
<input type="checkbox" id="dragmode" name="dragmode" />
</label>
</div>
<canvas width="300" height="300" id="fabric"></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.15/fabric.min.js"></script>