daybrush / moveable

Moveable! Draggable! Resizable! Scalable! Rotatable! Warpable! Pinchable! Groupable! Snappable!
https://daybrush.com/moveable/
MIT License
10.08k stars 618 forks source link

React nested SVG elements break the position #1011

Open maloguertin opened 1 year ago

maloguertin commented 1 year ago

Environments

Description

Nesting SVG elements breaks the positioning of moveable. getElementInfo(target) returns NaN for most matrixes.

daybrush commented 1 year ago

@maloguertin

The address code appears to have changed. Can you show which svg is causing the problem?

maloguertin commented 1 year ago

https://codesandbox.io/s/brave-moon-d3vjrq?file=/src/App.tsx

maloguertin commented 1 year ago

I played in moveable code a little bit and was able to get it somewhat working but I'm afraid it would break other stuff. From what I have seen there seem to be some probles in these areas:

//packages/react-moveable/src/utils.tsx

export function getSVGViewBox(el: SVGSVGElement) {
    const clientWidth = el.clientWidth;
    const clientHeight = el.clientHeight;

    if (!el) {
        return { x: 0, y: 0, width: 0, height: 0, clientWidth, clientHeight };
    }
    const viewBox = el.viewBox;
    const baseVal = (viewBox && viewBox.baseVal) || { x: 0, y: 0, width: 0, height: 0 }; 👈  it seems like nested SVGs will always return { x: 0, y: 0, width: 0, height: 0 }; for their baseVal 

    return {
        x: baseVal.x,
        y: baseVal.y,
        width: baseVal.width || clientWidth,
        height: baseVal.height || clientHeight,
        clientWidth,
        clientHeight,
    };
}

export function getSize( 👈 this function returns invalid values for a nested SVG, I tried playing around with instead returning the size of it's ownerSVGElement
    target?: SVGElement | HTMLElement | null,
): ElementSizes {
    const hasOffset = target && !isUndefined((target as any).offsetWidth);

    let offsetWidth = 0;
    let offsetHeight = 0;
    let clientWidth = 0;
    let clientHeight = 0;
    let cssWidth = 0;
    let cssHeight = 0;
    let contentWidth = 0;
    let contentHeight = 0;

    let minWidth = 0;
    let minHeight = 0;
    let minOffsetWidth = 0;
    let minOffsetHeight = 0;

    let maxWidth = Infinity;
    let maxHeight = Infinity;
    let maxOffsetWidth = Infinity;
    let maxOffsetHeight = Infinity;
    let inlineCSSWidth = 0;
    let inlineCSSHeight = 0;
    let svg = false;

    if (target) {
        if (!hasOffset && target!.tagName.toLowerCase() !== "svg") {
            // check svg elements
            const bbox = (target as SVGGraphicsElement).getBBox();

            svg = true;
            offsetWidth = bbox.width;
            offsetHeight = bbox.height;
            cssWidth = offsetWidth;
            cssHeight = offsetHeight;
            contentWidth = offsetWidth;
            contentHeight = offsetHeight;
            clientWidth = offsetWidth;
            clientHeight = offsetHeight;
        } else {
            // check html elements
            const getStyle = getCachedStyle(target);
            const targetStyle = target.style;
            const boxSizing = getStyle("boxSizing") === "border-box";
            const borderLeft = parseFloat(getStyle("borderLeftWidth")) || 0;
            const borderRight = parseFloat(getStyle("borderRightWidth")) || 0;
            const borderTop = parseFloat(getStyle("borderTopWidth")) || 0;
            const borderBottom = parseFloat(getStyle("borderBottomWidth")) || 0;
            const paddingLeft = parseFloat(getStyle("paddingLeft")) || 0;
            const paddingRight = parseFloat(getStyle("paddingRight")) || 0;
            const paddingTop = parseFloat(getStyle("paddingTop")) || 0;
            const paddingBottom = parseFloat(getStyle("paddingBottom")) || 0;

            const horizontalPadding = paddingLeft + paddingRight;
            const verticalPadding = paddingTop + paddingBottom;
            const horizontalBorder = borderLeft + borderRight;
            const verticalBorder = borderTop + borderBottom;
            const horizontalOffset = horizontalPadding + horizontalBorder;
            const verticalOffset = verticalPadding + verticalBorder;
            const position = getStyle("position");

            let containerWidth = 0;
            let containerHeight = 0;

            // SVGSVGElement, HTMLElement
            if ("clientLeft" in target) {
                let parentElement: HTMLElement | null = null;

                if (position === "absolute") {
                    const offsetInfo = getOffsetInfo(target, getDocumentBody(target));
                    parentElement = offsetInfo.offsetParent;

                } else {
                    parentElement = target.parentElement;
                }
                if (parentElement) {
                    const getParentStyle = getCachedStyle(parentElement);

                    containerWidth = parseFloat(getParentStyle("width"));
                    containerHeight = parseFloat(getParentStyle("height"));
                }
            }
            minWidth = Math.max(
                horizontalPadding,
                convertUnitSize(getStyle("minWidth"), containerWidth) || 0,
            );
            minHeight = Math.max(
                verticalPadding,
                convertUnitSize(getStyle("minHeight"), containerHeight) || 0,
            );
            maxWidth = convertUnitSize(getStyle("maxWidth"), containerWidth);
            maxHeight = convertUnitSize(getStyle("maxHeight"), containerHeight);

            if (isNaN(maxWidth)) {
                maxWidth = Infinity;
            }
            if (isNaN(maxHeight)) {
                maxHeight = Infinity;
            }
            inlineCSSWidth = convertUnitSize(targetStyle.width, 0) || 0;
            inlineCSSHeight = convertUnitSize(targetStyle.height, 0) || 0;
            cssWidth = parseFloat(getStyle("width")) || 0;
            cssHeight = parseFloat(getStyle("height")) || 0;

            contentWidth = abs(cssWidth - inlineCSSWidth) < 1
                ? between(minWidth, inlineCSSWidth || cssWidth, maxWidth)
                : cssWidth;
            contentHeight = abs(cssHeight - inlineCSSHeight) < 1
                ? between(minHeight, inlineCSSHeight || cssHeight, maxHeight)
                : cssHeight;

            offsetWidth = contentWidth;
            offsetHeight = contentHeight;
            clientWidth = contentWidth;
            clientHeight = contentHeight;

            if (boxSizing) {
                maxOffsetWidth = maxWidth;
                maxOffsetHeight = maxHeight;
                minOffsetWidth = minWidth;
                minOffsetHeight = minHeight;
                contentWidth = offsetWidth - horizontalOffset;
                contentHeight = offsetHeight - verticalOffset;
            } else {
                maxOffsetWidth = maxWidth + horizontalOffset;
                maxOffsetHeight = maxHeight + verticalOffset;
                minOffsetWidth = minWidth + horizontalOffset;
                minOffsetHeight = minHeight + verticalOffset;
                offsetWidth = contentWidth + horizontalOffset;
                offsetHeight = contentHeight + verticalOffset;
            }
            clientWidth = contentWidth + horizontalPadding;
            clientHeight = contentHeight + verticalPadding;
        }
    }

    return {
        svg,
        offsetWidth,
        offsetHeight,
        clientWidth,
        clientHeight,
        contentWidth,
        contentHeight,
        inlineCSSWidth,
        inlineCSSHeight,
        cssWidth,
        cssHeight,
        minWidth,
        minHeight,
        maxWidth,
        maxHeight,
        minOffsetWidth,
        minOffsetHeight,
        maxOffsetWidth,
        maxOffsetHeight,
    };
}
daybrush commented 1 year ago

@maloguertin

It may not be a normal structure, but I will try to modify it.

Thank you.

maloguertin commented 1 year ago

@maloguertin

It may not be a normal structure, but I will try to modify it.

Thank you.

You mean nested SVG may not be a normal structure?

daybrush commented 12 months ago

@maloguertin

moveable's new version is released. Check it again.

I think svg within svg has a strange structure.

A slightly more complicated calculation formula has been added. There may be a problem, but if there is, please let me know in the comments.

<svg>
    <svg>...</svg>
</svg>