UNPKG

any-grid-layout

Version:

This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.

335 lines (296 loc) 14.1 kB
import Sync from "@/units/grid/other/Sync.js"; import {cloneDeep, merge, parseContainer, throttle} from "@/units/grid/other/tool.js"; import ItemPos from "@/units/grid/ItemPos.js"; import DomFunctionImpl from "@/units/grid/DomFunctionImpl.js"; import {defaultStyle} from "@/units/grid/style/defaultStyle.js"; import EditEvent from '@/units/grid/other/EditEvent.js' import TempStore from "@/units/grid/other/TempStore.js"; const tempStore = TempStore.containerStore /** 栅格成员, 所有对 DOM的操作都是安全异步执行且无返回值,无需担心获取不到document * @param {Element} el 传入的原生Element * @param {Object} pos 一个包含Item位置信息的对象 * */ export default class Item extends DomFunctionImpl { //------------实例化Item外部传进的的参数,不建议在Container中传进来--------------// el = '' // 和Container不同的是,这里只能是原生的Element而不用id或者class,因为已经拿到Element传进来,没必要画蛇添足 name = '' // 开发者直接在元素标签上使用name作为名称,后续便能直接通过该名字找到对应的Item transition = { time: 180, field: 'top,left,width,height' } // time:动画过渡时长 ms, field: 要过渡的css字段 可通过Container.animation函数修改全部Item,通过Item.animation函数修改单个Item draggable = false // 自身是否可以拖动 resize = false // 自身是否可以调整大小 //----实例化Container外部传进的的参数,和Container一致,不可修改,不然在网格中会布局混乱----// margin = [null, null] // 间距 [左右, 上下] size = [null, null] // 宽高 [宽度, 高度] //----------------内部需要的参数---------------------// i = null // 每次重新布局给的自动正整数编号,对应的是Item的len element = null container = null // 挂载在哪个container上 tagName = 'div' classList = [] attr = [] pos = {} parentElement = null //----------------保持状态所用参数---------------------// _resizeTabEl = null _mounted = false __temp__ = { // -------------不可写变量--------------// static: false, // 实例化后就不能改变 //------------都是可写变量--------------// isNestingContainer: false, // 指示该Item是不是嵌套另一个Container eventRecord: {}, // 当前编辑状态开启的功能,drag || resize eventRunning: false, event: {}, styleLock: false, maskEl: null, height: 0, width: 0, clientWidth: 0, clientHeight: 0, resized: { w: 1, h: 1 } } constructor(itemOption) { super() // console.log(itemOption); merge(this, itemOption) if (this.el instanceof Element) this.element = this.el this.pos = new ItemPos(itemOption) // 只是初始化用,初始化后后面都是由ItemPosList管理,目前ItemPosList只是用于存储,也无大用 } /** 渲染, 直接渲染添加到 Container 中*/ mount() { Sync.run(() => { if (this._mounted) return // console.log(this.element); if (this.element === null) this.element = document.createElement(this.tagName) this.container.element.appendChild(this.element) // console.log(this.element); this.classList = Array.from(this.element.classList) this.attr = Array.from(this.element.attributes) this.updateStyle(defaultStyle.itemDefaults) this.updateStyle(this._genItemStyle()) this.edit({ draggable: this.draggable, resize: this.resize, }) this.animation(false) // this.__temp__.clientWidth = this.element.clientWidth // this.__temp__.clientHeight = this.element.clientHeight this.__temp__.w = this.pos.w this.__temp__.h = this.pos.h this.__temp__.static = this.pos.static this.element._gridItem_ = this this.element._isGridItem_ = true this._mounted = true // if (this.pos.static) this.element.innerHTML = ` // ${this.pos.i}</br> // ${this.pos.w},${this.pos.h}</br> // ${this.pos.x},${this.pos.y} ` // else this.element.innerHTML = this.i }) } _mask_(isMask = false) { if (isMask) { const maskEl = document.createElement('div') maskEl.classList.add('item-mask') this.updateStyle({ backgroundColor: 'transparent', height: this.element.clientHeight + 'px', width: this.element.clientWidth + 'px', position: 'absolute', left: '0', top: '0', }, maskEl) this.__temp__.maskEl = maskEl this.element.appendChild(maskEl) } if (this.__temp__.maskEl !== null && !isMask) { try { // 和Container联动的话在Container可能已经被清除掉了,这里只是尝试再次清理 this.element.removeChild(this.__temp__.maskEl) }catch (e) { } } } /** 自身调用从container中移除,未删除Items中的占位,若要删除可以遍历删除或者直接调用clear清除全部Item */ unmount() { Sync.run(() => { EditEvent.removeEventFromItem(this) this.container.element.removeChild(this.element) }) this._mounted = false } /** 将自己从Items列表中移除 */ remove() { this.container.engine.remove(this) } /** 为该Item开启编辑模式,这里代码和Container重复是因为可能单独开Item编辑模式 * @param {Object} editOption 包含 draggable(Boolean) resize(Boolean) 表示开启或关闭哪个功能, * 调用该函数不传参或者传入布尔值 true表示draggable和 resize 全部开启 * 传入 布尔值 false 表示全部关闭 * */ edit(editOption) { const eventRecord = { draggable: null, resize: null, } if (typeof editOption === 'object') { if (Object.keys(editOption).length === 0) editOption = true } if (editOption === false) { editOption = {draggable: false, resize: false} } else if (editOption === true) { editOption = {draggable: true, resize: true} } // console.log(editOption); if (this.draggable && editOption.draggable) eventRecord.draggable = null // 之前已经开了,不做操作(忽略) if (this.resize && editOption.resize) eventRecord.resize = null if (this.draggable && !editOption.draggable) eventRecord.draggable = false // 之前开了 ,现在要关闭 (关闭) if (this.resize && !editOption.resize) eventRecord.resize = false if (!this.draggable && editOption.draggable) eventRecord.draggable = true // 之前关闭的 ,现在要开启 (开启) if (!this.resize && editOption.resize) eventRecord.resize = true this.draggable = editOption.draggable this.resize = editOption.resize if (!this.__temp__.eventRunning){ if (this.resize === true){ this.handleResize(true, 'mount') } } if (this.draggable || this.resize) { EditEvent.startEvent(null, this, eventRecord) this.__temp__.eventRunning = true } else if (!this.draggable && !this.resize) { this.__temp__.eventRunning = false EditEvent.removeEvent(null, this, eventRecord) } if (this.resize === false) this.handleResize(this.resize, 'mount') } /** 对该Item开启位置变化过渡动画 * @param {Number|Object|Boolean} transition Item移动或者大小癌变要进行变化过渡的时间,单位ms,可以传入true使用默认时间180ms,或者传入false关闭动画 * @param {String} fieldString 要进行变化的CSS属性字段,使用逗号 * */ animation(transition, fieldString = null) { if (typeof transition === "object") { const cloneObj = cloneDeep(transition) transition = cloneObj.time fieldString = cloneObj.field } Sync.run(() => { const style = {} if (typeof transition === 'number') this.transition.time = transition // 传入数字保存 else if (transition === true) { transition = this.transition.time // 传入true 使用默认动画时长和字段 fieldString = this.transition.field } if (fieldString === null) fieldString = this.transition.field style.transitionProperty = transition ? fieldString : 'none' // 当transition = false的时候,不会开启动画 style.transitionDuration = transition ? transition + 'ms' : 'none' this.updateStyle(style) }) } /** 根据 pos的最新数据 立即更新当前Item在容器中的位置 */ updateItemLayout() { this.updateStyle(this._genItemStyle()) } handleResize(isResize = false, msg) { if (isResize && this._resizeTabEl === null) { const className = 'resizable-handle' const handleResizeEls = this.element.getElementsByClassName(className) const resizeTab = document.createElement('span') resizeTab.classList.add(className) resizeTab.innerHTML = '⊿' this.updateStyle(defaultStyle.handleResize, resizeTab) this._resizeTabEl = resizeTab this.element.appendChild(resizeTab) } else { if (this._resizeTabEl === null) return this.element.removeChild(this._resizeTabEl) } } /** @return 根据当前自身的this.pos 生成当前Item 距离父元素左边的距离, Item左边框 ----> 父元素左边框 */ offsetLeft() { let marginWidth = 0 if ((this.pos.x) > 1) marginWidth = (this.pos.x - 1) * this.margin[0] return ((this.pos.x - 1) * this.size[0]) + marginWidth } /** @return 根据当前自身的this.pos 生成当前Item 距离父元素顶部边的距离, Item上边框 ----> 父元素上边框 */ offsetTop() { let marginHeight = 0 if ((this.pos.y) > 1) marginHeight = (this.pos.y - 1) * this.margin[1] return ((this.pos.y - 1) * this.size[1]) + marginHeight } /** @return 获取该Item 当前的宽度 */ nowWidth = () => { let marginWidth = 0 if ((this.pos.w) > 1) marginWidth = (this.pos.w - 1) * this.margin[0] // console.log(this.pos.w, marginWidth); return (this.pos.w * this.size[0]) + marginWidth } /** @return 获取该Item 当前的高度 */ nowHeight = () => { let marginHeight = 0 if ((this.pos.h) > 1) marginHeight = (this.pos.h - 1) * this.margin[1] // console.log(this.pos.h, marginHeight); return (this.pos.h * this.size[1]) + marginHeight } /** @return 根据当前自身的this.pos 生成Item当前必须占用最小宽度的像素大小 */ minWidth() { let marginWidth = 0 if (this.pos.minW === Infinity) return Infinity if ((this.pos.minW) > 1) marginWidth = (this.pos.minW - 1) * this.margin[0] return (this.pos.minW * this.size[0]) + marginWidth } /** @return 根据当前自身的this.pos 生成Item当前必须占用最小高度的像素大小 */ minHeight = () => { let marginHeight = 0 if (this.pos.minH === Infinity) return Infinity if ((this.pos.minH) > 1) marginHeight = (this.pos.minH - 1) * this.margin[1] return (this.pos.minH * this.size[1]) + marginHeight } /** @return 根据当前自身的this.pos 生成Item当前必须占用最大宽度的像素大小 */ maxWidth() { let marginWidth = 0 if (this.pos.maxW === Infinity) return Infinity marginWidth = (this.pos.maxW - 1) * this.margin[0] return (this.pos.maxW * this.size[0]) + marginWidth } /** @return 根据当前自身的this.pos 生成Item当前必须占用最大高度的像素大小 */ maxHeight = () => { let marginHeight = 0 if (this.pos.maxH === Infinity) return Infinity marginHeight = (this.pos.maxH - 1) * this.margin[1] return (this.pos.maxH * this.size[1]) + marginHeight } /** 是否锁定CSS 样式的渲染 不传参数返回当前的状态 ,传布尔参数将状态设置成相应的布尔值 */ styleLock(isStyle = null) { if (isStyle === null) return this.__temp__.styleLock if (isStyle === true) return this.__temp__.styleLock = true if (isStyle === false) return this.__temp__.styleLock = false } /** 生成该ITEM的栅格放置位置样式 */ _genItemStyle = () => { // console.log(this.offsetLeft(),this.offsetTop()); if (this.styleLock()) return {} // 三种布局方案,都实现了grid布局 // return { width: this.nowWidth() + 'px', height: this.nowHeight() + 'px', // gridColumn: `${this.pos.x} / span ${this.pos.w}`, // gridRow: `${this.pos.y} / span ${this.pos.h}`, left: this.offsetLeft() + 'px', top: this.offsetTop() + 'px', // transform:`translate(${this.offsetLeft()+'px'},${this.offsetTop()+'px'})`, } } _genLimitSizeStyle = () => { if (this.styleLock()) return {} return { minWidth: this.minWidth() + 'px', minHeight: this.minHeight() + 'px', maxWidth: this.maxWidth() + 'px', maxHeight: this.maxHeight() + 'px', } } }