Содержит повернутый объект внутри другого повернутого объекта FabricJS - PullRequest
4 голосов
/ 24 марта 2020

У меня есть два объекта: родитель (красный) и ребенок (синий). Родительский объект зафиксирован и не может быть перемещен , только дочерний объект является подвижным, а дочерний объект всегда больше, чем родительский. Каким бы образом ни перемещался дочерний объект, он всегда должен содержаться внутри дочернего объекта, что означает, что мы никогда не должны видеть красный прямоугольник.

enter image description here

Демонстрация: https://codesandbox.io/s/force-contain-of-object-inside-another-object-fabric-js-7nt7q

Я знаю, что есть решения для удержания объекта внутри холста или других границ объекта (например, Перемещение объекта в пределах границы холста ), которые в основном приводят к верхние / правые / нижние / левые значения не должны превышать родительские значения, но здесь мы имеем случай двух повернутых объектов на одну и ту же степень .

У меня есть реальный сценарий когда пользователь загружает фотографию в рамочный контейнер. Фотография обычно всегда больше рамки контейнера. Пользователь может переместить фотографию внутри рамки, но ему нельзя разрешать создавать какие-либо пустые места, фотография всегда должна содержаться внутри рамки для фотографий.

Ответы [ 2 ]

1 голос
/ 27 марта 2020

Я бы go с чистым холстом (без фабрики cjs) , делай это с нуля так, чтобы ты хорошо понимал проблему, с которой сталкиваешься, тогда, если тебе это нужно, тот же лог c должен легко переноситься в любую библиотеку.

У вас есть несколько правил:

  • Родительский объект зафиксирован и не может быть перемещен,
  • Дочерний объект является подвижным.
  • Дочерний объект всегда больше родительского.
  • Дочерний объект всегда ограничен родительским объектом.

Так что моя идея состоит в том, чтобы получить все четыре угла, чтобы на ходу мы могли использовать эти координаты, чтобы определить, может ли он Если вы будете перемещены в новое место или нет, код должен быть легко понятен, но спросите, есть ли у вас какие-либо проблемы.

Я использую алгоритм приведения лучей:
https://github.com/substack/point-in-polygon/blob/master/index.js
С этим все, что нам нужно сделать, это проверить, что углы ребенка не находятся внутри родитель и то, что родитель находится внутри ребенка, вот и все.

Я не специалист по Фабри cJS, поэтому мой лучший результат может быть не очень ...
но ниже моя попытка заставить ваш код работать.

<canvas id="canvas" width="500" height="350"></canvas>

<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<script>
    var canvas = new fabric.Canvas("canvas");
    canvas.stateful = true;

    function getCoords(rect) {
        var x = rect.left;
        var y = rect.top;
        var angle = (rect.angle * Math.PI) / 180;

        var coords = [{ x, y }];
        x += rect.width * Math.cos(angle);
        y += rect.width * Math.sin(angle);
        coords.push({ x, y });

        angle += Math.PI / 2;
        x += rect.height * Math.cos(angle);
        y += rect.height * Math.sin(angle);
        coords.push({ x, y });

        angle += Math.PI / 2;
        x += rect.width * Math.cos(angle);
        y += rect.width * Math.sin(angle);
        coords.push({ x, y });
        return coords;
    }

    function inside(p, vs) {
        var inside = false;
        for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
            var xi = vs[i].x, yi = vs[i].y;
            var xj = vs[j].x, yj = vs[j].y;
            var intersect =
                yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / (yj - yi) + xi;
            if (intersect) inside = !inside;
        }
        return inside;
    }

    var parent = new fabric.Rect({
        width: 150, height: 100, left: 200, top: 50, angle: 25, selectable: false, fill: "red"
    });
    var pCoords = getCoords(parent);

    var child = new fabric.Rect({
        width: 250, height: 175, left: 180, top: 10, angle: 25, hasControls: false, fill: "rgba(0,0,255,0.9)"
    });

    canvas.add(parent);
    canvas.add(child);

    canvas.on("object:moving", function (e) {
        var cCoords = getCoords(e.target);
        var inBounds = true;
        cCoords.forEach(c => { if (inside(c, pCoords)) inBounds = false; });
        pCoords.forEach(c => { if (!inside(c, cCoords)) inBounds = false; });
        if (inBounds) {
            e.target.setCoords();
            e.target.saveState();
            e.target.set("fill", "rgba(0,0,255,0.9)");            
        } else {
            e.target.set("fill", "black");
            e.target.animate({
                left: e.target._stateProperties.left,
                top: e.target._stateProperties.top
              },{
                duration: 500,
                onChange: canvas.renderAll.bind(canvas),
                easing: fabric.util.ease["easeInBounce"],
                onComplete: function() {
                  e.target.set("fill", "rgba(0,0,255,0.9)");
                }
            });
        }
    });
</script>

Этот код также находится в песочнице:
https://codesandbox.io/s/force-contain-of-object-inside-another-object-fabric-js-dnvb5

Конечно, приятно не беспокоиться о кодировании все щелчки / удержания / перетаскивания fabri c делают это действительно легко ...

Я экспериментировал с Fabri cJS и там было замечательное свойство canvas
(canvas.stateful = true; )
, который позволяет нам отслеживать, где мы были, и если мы go вышли за пределы, мы можем отменить это движение, также играя с анимацией, которая дает пользователю визуальную обратную связь о том, что движение не разрешено.

Вот еще одна версия без анимации:

<canvas id="canvas" width="500" height="350"></canvas>

<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<script>
    var canvas = new fabric.Canvas("canvas");
    canvas.stateful = true;

    function getCoords(rect) {
        var x = rect.left;
        var y = rect.top;
        var angle = (rect.angle * Math.PI) / 180;

        var coords = [{ x, y }];
        x += rect.width * Math.cos(angle);
        y += rect.width * Math.sin(angle);
        coords.push({ x, y });

        angle += Math.PI / 2;
        x += rect.height * Math.cos(angle);
        y += rect.height * Math.sin(angle);
        coords.push({ x, y });

        angle += Math.PI / 2;
        x += rect.width * Math.cos(angle);
        y += rect.width * Math.sin(angle);
        coords.push({ x, y });
        return coords;
    }

    function inside(p, vs) {
        var inside = false;
        for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
            var xi = vs[i].x, yi = vs[i].y;
            var xj = vs[j].x, yj = vs[j].y;
            var intersect =
                yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / (yj - yi) + xi;
            if (intersect) inside = !inside;
        }
        return inside;
    }

    var parent = new fabric.Rect({
        width: 150, height: 100, left: 200, top: 50, angle: 25, selectable: false, fill: "red"
    });
    var pCoords = getCoords(parent);

    var child = new fabric.Rect({
        width: 250, height: 175, left: 180, top: 10, angle: 25, hasControls: false, fill: "rgba(0,0,255,0.9)"
    });

    canvas.add(parent);
    canvas.add(child);

    canvas.on("object:moving", function (e) {
        var cCoords = getCoords(e.target);
        var inBounds = true;
        cCoords.forEach(c => { if (inside(c, pCoords)) inBounds = false; });
        pCoords.forEach(c => { if (!inside(c, cCoords)) inBounds = false; });
        if (inBounds) {
            e.target.setCoords();
            e.target.saveState();
        } else {
            e.target.left = e.target._stateProperties.left;
            e.target.top = e.target._stateProperties.top;
        }
    });
</script>

Этот алгоритм также открывает двери и для других фигур, вот шестигранная версия:
https://raw.githack.com/heldersepu/hs-scripts/master/HTML/canvas_contained2.html

0 голосов
/ 26 марта 2020

вы можете просто создать новый Class с вашим object внутри и выполнять все действия только с parent из children, что-то вроде этого:

 fabric.RectWithRect = fabric.util.createClass(fabric.Rect, {
      type: 'rectWithRect',
      textOffsetLeft: 0,
      textOffsetTop: 0,
      _prevObjectStacking: null,
      _prevAngle: 0,
      minWidth: 50,
      minHeight: 50,   

      _currentScaleFactorX: 1,
      _currentScaleFactorY: 1,

      _lastLeft: 0,
      _lastTop: 0,

      recalcTextPosition: function () {
           //this.insideRect.setCoords();
           const sin = Math.sin(fabric.util.degreesToRadians(this.angle))
           const cos = Math.cos(fabric.util.degreesToRadians(this.angle))
           const newTop = sin * this.insideRectOffsetLeft + cos * this.insideRectOffsetTop
           const newLeft = cos * this.insideRectOffsetLeft - sin * this.insideRectOffsetTop
           const rectLeftTop = this.getPointByOrigin('left', 'top')
           this.insideRect.set('left', rectLeftTop.x + newLeft)
           this.insideRect.set('top', rectLeftTop.y + newTop)
           this.insideRect.set('width', this.width - 40)
           this.insideRect.set('height', this.height - 40)
           this.insideRect.set('scaleX', this.scaleX)
           this.insideRect.set('scaleY', this.scaleY)
      },

     initialize: function (textOptions, rectOptions) {
       this.callSuper('initialize', rectOptions)

       this.insideRect = new fabric.Rect({
         ...textOptions,
         dirty: false,
         objectCaching: false,
         selectable: false,
         evented: false,
         fragmentType: 'rectWidthRect'
       });
       canvas.bringToFront(this.insideRect);
       this.insideRect.width = this.width - 40;
       this.insideRect.height = this.height - 40;
       this.insideRect.left = this.left + 20;
       this.insideRect.top = this.top + 20;
       this.insideRectOffsetLeft = this.insideRect.left - this.left
       this.insideRectOffsetTop = this.insideRect.top - this.top


       this.on('moving', function(e){
         this.recalcTextPosition();
       })

       this.on('rotating',function(){
         this.insideRect.rotate(this.insideRect.angle + this.angle - this._prevAngle)
         this.recalcTextPosition()
         this._prevAngle = this.angle
       })

       this.on('scaling', function(fEvent){
           this.recalcTextPosition();
       });

       this.on('added', function(){
         this.canvas.add(this.insideRect)
       });

       this.on('removed', function(){
         this.canvas.remove(this.insideRect)
       });

       this.on('mousedown:before', function(){
         this._prevObjectStacking = this.canvas.preserveObjectStacking
         this.canvas.preserveObjectStacking = true
       });

       this.on('deselected', function(){
         this.canvas.preserveObjectStacking = this._prevObjectStacking
       });

     }
 });

, а затем просто добавьте свой element к своему canvas как обычно:

var rectWithRect = new fabric.RectWithRect(
            {
              fill: "red",
            }, // children rect options
           {
             left:100,
             top:100,
             width: 300,
             height: 100,
             dirty: false,
             objectCaching: false,
             strokeWidth: 0,
             fill: 'blue'
           } // parent rect options
      );


canvas.add(rectWithRect);

кстати, вы можете использовать такой метод для создания nested элементов, текста с фоном и других.

Коды и ящики ДЕМО

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...