react使用拖拽,缩放组件,采用react-rnd解决 -完整版

发布于:2025-03-12 ⋅ 阅读:(15) ⋅ 点赞:(0)

屏幕录制2025-03-10 10.16.06

以下代码仅提供左侧可视化区域
右侧数据根据你们的存储数据来
大家直接看Rnd标签设置的属性即可!!!!!

/**
 * 用户拖拽水印的最终位置信息
 */
export interface ProductWatermarkValue {
    watermark?: ProductWatermarkManagerValue;
    position: {
        x: number; // 水印在图片上的水平位置
        y: number; // 水印在图片上的垂直位置
    };
    size: {
        width: number; // 水印的宽度(相对于商品图片的宽度)
        height: number; // 水印的高度(相对于商品图片的高度)
    };
}
/**
 * 用户上传的水印详细信息,比如路径,宽高
 */
export interface ProductWatermarkManagerValue {
    id: string;
    name: string;
    fileUrl: string;
    width: number;
    height: number;
    type: ProductWatermarkManagerValueType;
}
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Rnd } from 'react-rnd';
import { Dispatch } from 'redux';
import { actions } from '@comall-backend-builder/core';
import { Entity } from '@comall-backend-builder/core/lib/parser';
import './index.less';

const prefix = 'product-main-image-watermark-preview';
interface Props {
    dispatch: Dispatch;
    entity: Entity;
    componentId: any;
    preview: any;
}
interface State {
    /**
     * 正在操作中
     */
    isDragging: boolean;
}
export class productMainImageWatermarkRulePreview extends Component<Props, State> {
    constructor(props: Props) {
        super(props);
        this.state = {
            isDragging: false,
        };
    }
    getWatermarkRule = () => {
        const { preview } = this.props;
        return preview?.baseInfo?.watermarkRule;
    };
    getPreviewProduct = () => {
        const { preview } = this.props;
        const goods = preview?.baseInfo?.goods || [];
        const isPreviewGoods = goods.find((item: any) => {
            return item.isPreview;
        });
        return isPreviewGoods;
    };
    onChangeWatermarkRule = (ruleWatermark: any) => {
        const { dispatch, componentId } = this.props;
        dispatch(actions.formChangeAction(componentId, 'baseInfo.watermarkRule', ruleWatermark));
    };
    handleDragStart = (e: any) => {
        e.preventDefault();
        e.stopPropagation();
        this.setState({
            isDragging: true,
        });
    };
    handleDragStop = (e: any, d: any) => {
        e.preventDefault();
        e.stopPropagation();
        this.setState({
            isDragging: false,
        });
        const watermarkRule = this.getWatermarkRule();
        watermarkRule.position = {
            x: d.x > 0 ? d.x * 2 : 0,
            y: d.y > 0 ? d.y * 2 : 0,
        };
        this.onChangeWatermarkRule({ ...watermarkRule });
    };
    handleResizeStart = (e: any) => {
        e.preventDefault();
        e.stopPropagation();
        this.setState({
            isDragging: true,
        });
    };
    handleResizeStop = (e: any, direction: any, ref: any, delta: any, position: any) => {
        e.preventDefault();
        e.stopPropagation();
        this.setState({
            isDragging: false,
        });
        const watermarkRule = this.getWatermarkRule();
        const sizeWidth = ref.style.width.replace('px', '');
        const sizeHeight = ref.style.height.replace('px', '');
        //因为左侧模拟器是375px,后端存储的是750px的,所以Rnd数据需要乘以2
        watermarkRule.size = {
            width: `${sizeWidth * 2}px`,
            height: `${sizeHeight * 2}px`,
        };
        watermarkRule.position = {
            x: position.x > 0 ? position.x * 2 : 0,
            y: position.y > 0 ? position.y * 2 : 0,
        };
        this.onChangeWatermarkRule({ ...watermarkRule });
    };
    render() {
        const watermarkRule = this.getWatermarkRule();
        if (!watermarkRule) {
            return null;
        }
        const { position, size, watermark } = watermarkRule;
        const previewGoods = this.getPreviewProduct();
        const pic = previewGoods?.productPic || '';
        const { isDragging } = this.state;
        const style = {
            backgroundImage: pic ? `url(${pic})` : undefined,
        };
        const sizeWidth = size && size.width ? size.width.replace('px', '') : 0;
        const sizeHeight = size && size.height ? size.height.replace('px', '') : 0;
        //因为后端存储的是750px的,左侧模拟器是375px,所以页面渲染数据需要除2
        const rndSize = {
            width: `${sizeWidth / 2}px`,
            height: `${sizeHeight / 2}px`,
        };
        const rndPosition = {
            x: position.x / 2,
            y: position.y / 2,
        };
        console.log('存储大小size,position', size, position);
        console.log('展示大小rndSize,rndPosition', rndSize, rndPosition);
        const isDraggingStyle = {
            opacity: isDragging ? 0.8 : 1,
            border: isDragging ? '2px solid #1890ff' : undefined,
        };
        return (
            <div className={prefix}>
                <div className={`${prefix}__bg`} style={style}>
                    <Rnd
                        maxHeight={375}
                        maxWidth={375}
                        size={rndSize}
                        position={rndPosition}
                        bounds="parent"
                        onDragStart={this.handleDragStart}
                        onDragStop={this.handleDragStop}
                        onResizeStart={this.handleResizeStart}
                        onResizeStop={this.handleResizeStop}
                        resizeParentMore={true} // 如果需要阻止父容器跟随大小变化,可以设置为false
                        enableResizing={{
                            top: true,
                            right: true,
                            bottom: true,
                            left: true,
                            topRight: true,
                            bottomRight: true,
                            bottomLeft: true,
                            topLeft: true,
                        }}
                        resizeHandles={['se', 'sw', 'ne', 'nw']}
                        style={isDraggingStyle}
                        onClick={(e: any) => e.stopPropagation()}
                        lockAspectRatio={true}
                    >
                        <img
                            src={watermark?.fileUrl}
                            alt=""
                            style={{ width: '100%', height: '100%' }}
                        />
                    </Rnd>
                </div>
            </div>
        );
    }
}

const mapStateToProps = (_state: any, props: any) => {
    let preview = null;
    let componentId = null;
    const entityId = props.entity.id;
    for (var compId in _state.components) {
        const comp = _state.components[compId];
        if (
            (comp.type === 'CreateForm' || comp.type === 'EditForm') &&
            comp.entityId === entityId
        ) {
            preview = comp.fields;
            componentId = compId;
        }
    }
    return { preview: preview, componentId: componentId };
};

export const ProductMainImageWatermarkRulePreview = connect(mapStateToProps)(
    productMainImageWatermarkRulePreview
);

.product-main-image-watermark-preview{
  &__bg{
    margin-right: 10px;
    overflow: hidden;
    width: 375px ;
    min-width: 375px;
    height: 375px;
    position: relative;
    border: 1px solid #ccc;
    background-position: center;
    background-repeat: no-repeat;
    background-size: cover;
  }
}

开发过程中遇到的问题
1.在使用过程中,火狐浏览器出现一拖拽,就打开了浏览器新标签页
解决方案:在方法调用处理中新增以下两个代码

e.preventDefault();
e.stopPropagation();

2.在使用过程中,用户需要自己上传的水印在左侧渲染中,拉伸时,是等比例放大或缩小的,而不是用户自己控制拉伸大小
解决方案:Rnd标签设置属性

  lockAspectRatio={true}

希望以上代码对大家有帮助❤️