yoyoo-ddr-vue3

Lightweight and no dependencies,use draggable, resizable, rotatable, configurable for vue 3 component

Usage no npm install needed!

<script type="module">
  import yoyooDdrVue3 from 'https://cdn.skypack.dev/yoyoo-ddr-vue3';
</script>

README

Install

npm i yoyoo-ddr --save

Usage

Two-way data binding(v-model)

import DDR from 'yoyoo-ddr'
import 'yoyoo-ddr/dist/yoyoo-ddr.css'

export default {
  data() {
    return {
      transform: { x: 100, y: 100, width: 100, height: 100, rotation: 0 },
    }
  },
  render() {
    return (
      <DDR v-model={this.transform}>
        <div style="background:red;width:100%;height:100%">x={this.transform.x}</div>
      </DDR>
    )
  },
}

Use arrays to render multiple components

export default {
  data() {
    return {
      list: [
        { id: 1, active: false, transform: { x: 100, y: 100, width: 100, height: 100, rotation: 0 } },
        { id: 2, active: false, transform: { x: 200, y: 100, width: 100, height: 100, rotation: 0 } },
        { id: 3, active: false, transform: { x: 300, y: 100, width: 100, height: 100, rotation: 0 } },
        { id: 4, active: false, transform: { x: 400, y: 100, width: 100, height: 100, rotation: 0 } },
      ],
    }
  },
  methods: {
    // If the `active` property of the component and the return value of the beforeActive function are both false,
    // the component will not respond to mouse operations
    handleActive(id) {
      //  Synchronize the array once when the component is selected
      this.list = this.list.map((item) => {
        if (item.id === id) {
          // Set the currently selected active to true in the array
          item = { ...item, active: true }
        } else if (item.active) {
          // Normally, only one component of the array rendering is active, so here the last selected component is set to the false state
          item = { ...item, active: false }
        }
        return item
      })
      return true
    },
    // Use the renderContent function instead of slot to ensure that only the modified data is updated when updating `list`.
    renderChild(item) {
      return <div style="background:red;width:100%;height:100%">Child {item.id}</div>
    },
  },
  render() {
    return (
      <div>
        {this.list.map((item) => {
          // If you use slot here, all components will be re-rendered when a new value is set for list. practice shows that performance problems occur when more than 200 components are used, while up to 2000 can be used with renderContent.

          // If you want to customize other properties for the component, you need to wrap the component once. You can refer to examples/vseditor/cell-wrapper.vue in the demo project.
          return (
            <DDR
              beforeActive={this.handleActive}
              active={item.active}
              key={item.id}
              id={item.id}
              value={item.transform}
              renderContent={this.renderChild}
            />
          )
        })}
      </div>
    )
  },
}

value One-way data binding

export default {
  data() {
    return {
      transform: { x: 100, y: 100, width: 100, height: 100, rotation: 0 },
    }
  },
  methods: {
    handleDrag(event, transform) {
      this.transform = transform
    },
    handleResize(event, transform) {
      this.transform = transform
    },
    handleRotate(event, transform) {
      this.transform = transform
    },
  },
  render() {
    // When the component is dragged, the `transform` of the parent component will not be updated
    // You need to listen to the event to synchronize the transform
    // This example is only the synchronization of a single component. If you use array rendering, it is recommended to use dragend, rotatend, and resizend events to synchronize to avoid performance problems caused by the update frequency too fast
    return (
      <DDR onResize={this.handleResize} onRotate={this.handleRotate} onDrag={this.handleDrag} value={this.transform}>
        <div style="background:red;width:100%;height:100%">child</div>
      </DDR>
    )
  },
}

Features

  • Lightweight, no third-party dependencies
  • Configurable drag, rotation, resize, snap to grid , limit movement within the parent element, fixed coordinate axis movement, etc.

Possible problems

  • If the container uses transform:scale(2), the position of the component will be wrong
  • Based on vue 2 , does not support vue3
  • If the container uses overflow:scroll, it will also cause the component to be in the wrong position when dragging.

Props

value(v-model)

Type: Object
Defaults: {x:0,y:0,width:100,height:100,rotation:0}
Required: true

One-way data flow. The position, rotation angle, size of the component. make sure the parent container is set to position=relative|absolute|fixed

  • x: Left of parent side,will add left={x}px to Dom
  • y: Top of parent side,will add top={y}pxto Dom
  • width: Width of the component,will add width={x}pxto Dom
  • height: Height of the component, will add height={x}px to Dom
  • rotation: Rotation of the component, will add transform=rotation({rotation}deg) to Dom

Example:

<DDR value={{ x: 100, y: 100, width: 100, height: 100, rotation: 45 }} />

draggable

Type: Boolean
Defaults: true
Required: false

Whether the component can be dragged

Example:

<DDR draggable={false} />

rotatable

Type: Boolean
Defaults: true
Required: false

Whether the component can be rotated

Example:

<DDR rotatable={false} />

resizable

Type: Boolean
Defaults: true
Required: false

Whether the component can be resized

Example:

<DDR resizable={false} />

active

Type: Boolean
Defaults: true
Required: false

When the value is false,The current component does not accept a response. it is equivalent to the same effect as draggable, rotatable, and resizable are all false

Example:

<DDR active={false} />

acceptRatio

Type: Boolean
Defaults: true
Required: false

When set to true will lock the aspect ratio of the component for resizing ,the same effect is achieved if the shift key is pressed

Example:

<DDR acceptRatio={true} />

parent

Type: Boolean
Defaults: true
Required: false

When set to true the component can only be moved and resized within the parent container, if the rotation angle is greater than 0 this property will be ignored when resizing

Example:

<DDR parent={true} />

resizeHandler

Type: Array
Defaults: ['tl','tm','tr','r','br','bm','bl','l']
Required: false

Define the array of handles to restrict the element resizing

  • tl: Top left
  • tm: Top middle
  • tr: Top right
  • r: Middle right
  • br: Bottom right
  • bm: Bottom middle
  • bl: Bottom left
  • l: Middle left

Example:

<DDR resizeHandler={['tl', 'tr', 'br', 'bl']} />

handlerSize

Type: Number
Defaults: 11
Required: false

Example:

<DDR handlerSize={7} />

minWidth

Type: Number
Defaults: 1
Required: false

Resizable minimum width

Example:

<DDR minWidth={40} />

minHeight

Type: Number
Defaults: 1
Required: false

Resizable minimum height

Example:

<DDR minHeight={40} />

maxWidth

Type: Number
Defaults: 100000000
Required: false

Resizable maximum width

Example:

<DDR minWidth={40} />

maxHeight

Type: Number
Defaults: 100000000
Required: false

Resizable maximum height

Example:

<DDR minHeight={40} />

grid

Type: Array
Defaults: [1,1]
Required: false

Define the grid on which the element is snapped. The format is [x,y]

Example:

<DDR grid={[10, 10]} />

axis

Type: String
Defaults: xy
Required: false

Specify the coordinate axis to drag, the default xy can be dragged, the optional values are x | y | xy

Example:

<DDR axis="x" />

id

Type: String
Defaults: ''
Required: false

When using arrays, the unique identifier of the component needs to be provided

Example:

<div>
  {this.list.map((item) => (
    <DDR key={item.id} id={item.id} />
  ))}
</div>

beforeActive

Type: (id:string)=> boolean
Defaults: (id)=> false
Required: false

When the component mouse is pressed, this function will be called and the ID of the component will be passed,This function is provided for array rendering. when both active and the function return value are false, the component will not respond to any operation. if true is returned, the component will accept the response even if active is false.

Example:

<DDR
  beforeActive={(id) => {
    console.log(id)
    return true
  }}
/>

renderContent

Type: (vm)=> VNode
Defaults: undefined
Required: false

This function returns your custom subcomponent

Example:

<DDR
  renderContent={(drag) => {
    console.log(drag)
    return <div>Child {drag.transform.x}</div>
  }}
/>

Custom class style

  • The mouse is pressed and ready to drag: ddr-ready-drag . Dragging: ddr-dragging
  • The mouse is pressed and ready to resize: ddr-ready-resize . Resizing: ddr-resizing
  • The mouse is pressed and ready to rotate: ddr-ready-rotate . Rotating: ddr-rotating
  • the component is selected: active

Events

A series of events are triggered when dragging, rotating and resizing. The event will pass in two parameters. The first parameter is the native event object, and the second parameter is the transformation of the current component.

name args
dragstart (event,transform)=>{} :void 0
drag (event,transform)=>{} :void 0
dragend (event,transform)=>{} :void 0
rotatestart (event,transform)=>{} :void 0
rotate (event,transform)=>{} :void 0
rotateend (event,transform)=>{} :void 0
resizestart (event,transform)=>{} :void 0
resize (event,transform)=>{} :void 0
resizeend (event,transform)=>{} :void 0
  • Template usage: <ddr @dragstart="handler" />
  • Render usage: <DDR onDragstart={handler} onDragend={handler} />

Slots

name: default

Example:

<DDR>
  <div style="width:100%;height:100%;background:red" />
</DDR>

If you use arrays you need to wrap the components

Example:

Container component

{

  data(){
    return {list:[]}
  },
  render(){
    return <div>
       {
         this.list.map(item=>{
           return  <DDRWrapper key={item.id} item={item}/>
         })
       }
    </div>
  }
}

DDRWrapper component

{
  props:['item'],
  render(){
    let {id} = this.item
    return (
      <DDR id={id}  >
        <div style="width:100%;height:100%;background:red" />
      </DDR>
    )
  }
}

Demo project

Link https://zuimeiaj.github.io/ddr/

The currently implemented functions of the Demo project and are constantly being updated

  • Can customize components,add implementation and export in the directory src/examples/vseditor/component-impl.js. Just add the corresponding type in components.vue.
  • Customizable component property editor Add the corresponding type in the directory src/examples/vseditor/prop-inspector.vue and implement it
  • You can drag and drop components from the component list to the editing area for editing
  • Support component nesting, the Container component in component-impl.js actually contains an editor-view component, which can be nested infinitely
  • Other functions in the editor area will be provided in the form of plug-ins to facilitate function management
  • Support historical record rollback, component deletion and canvas clearing, etc.
  • Support batch selection
  • Continue to refactor the code, usually it is implemented first and then optimized.
  • Add more components such as forms, charts, etc.
  • Add shortcut key functions, such as: delete, copy, add copy, cut, paste, etc.
  • Add the canvas component can generate simple graphics, such as curves, and other graphics.

The Demo project will continue to improve and add new functions, and its purpose is to add some useful functions to the component through a large number of project practices, so that it can meet the needs of more scenarios as much as possible.

Comments and suggestions

Welcome to make valuable comments and suggestions

License

The MIT License (MIT). Please see License File for more information.