moveable 一个可实现前端海报编辑器的 js 库

目录

懒得改文案了,海报编辑器换方案了,如果后面用别的再更。

缘由-胡扯

导火索:睡不着,现在是 25/1/25 快早上了

在这段时间突然有了一个新项目,该项目与前端通过 img 素材编辑一个海报有相同的需求点,又或者说该需求是弱于海报编辑的。

最开始,我是打算直接通过 js 直接实现对应的功能,但是总有一些小bug,刚好公司的前端推给我了 moveable,瞟了一眼后,发现需求完美符合,moveable 可实现前端对单个、多个元素的拖拽、组合、编辑、缩放、旋转、拉扯等操作,甚至于有一种"杀鸡焉用牛刀之感"。

但随着项目的迭代,未来的需求不可得知,但用发展的眼光看待这个项目的话,直接实现一个海报编辑器是最优的选择。

接下来我把我总结的 moveable 编写为教程,包括海报编辑器的制作写在该文之中,计划上下两篇,毕竟一篇使用基础,另一篇就是编辑器的制作。

想必,各位也不想在二次开发的时候看不懂 moveable 的实现逻辑吧,能平滑过渡就平滑过渡吧,大家的脑子都不想承载过多的计算,张飞:俺也一样。

本文实验环境

系统:Windows

前端:html

框架:无

js:其实我不是前端,就当是原生吧,因为笔者并不熟悉标准

编辑器:vs code

参考示例:https://daybrush.com/moveable/storybook/index.html

GitHub:https://github.com/daybrush/moveable

apidoc:https://daybrush.com/moveable/release/latest/doc/

通用流程

首先在此通过 vs 编写一个基础的 html (快捷键感叹号会自动弹出,选择单感叹号即可):

此时选择之后将会创建一个基础的 html 基础代码(如果没有的就直接复制吧):

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

1.基础移动

1.1 基础代码

我们首先实现 moveable 对元素的基础移动。

要实现 moveable 的基础移动需要创建一个 div 为其根容器,此时我们在 body 元素下创建一个 div ,并给与样式修饰 class="root",此处的样式修饰并不是必须项,在此只是为了更好的进行演示,当然,在 storybook 上给与的官方示例也是如此。

此时在基础的 html 元素中的 body 下,创建一个 div 为根节点,代码如下:

html 复制代码
<div class="root"></div>

那么此时根节点有了,接下来就需要创建 moveable 的容器。你听的没错,这是一个比较通用的概念,实现某个特殊的元素时,使用一个容器作为存储是一个非常常见的方式。

此时在根节点下,创建一个 div 为容器:

html 复制代码
<div class="root">
    <div class="container" data-croffle-ref="element$0"></div>
</div>

在以上的 html 代码中,样式修饰为 class="container" 则是容器的元素节点,但可能你对这个节点的疑问在 data-croffle-ref="element$0",如果没疑问就更好了,在此给一些不理解这个元素属性的读者做一下解释。

1.1.1 data-* 解释

在以上代码中 data-croffle-ref 为 html5 引入的一种机制,这个机制简单的来说是让开发者在不影响本身元素的语义的情况下,为当前元素增加新的元数据,又或者说开发者自定义了当前元素的属性

一般 data-* 自定义元数据可以与 document.querySelector 搭配,准确的找到对应的元数据,接下来在编写 js 代码时将会解释这一部分。

在这里,读者可以理解为此时定义了一个 moveable 的容器,并且自定义了一个属性 croffle-ref,但由于自定义属性的编写方式为带前缀 data-,即编写为 data-croffle-ref="element$0",其中 element$0 为这个自定义属性的值。

1.2 操作元素创建

当创建完操作元素的容器后,接下来创建用于控制的元素,则创建一个拖动、缩放、选择等操作的元素。

毕竟我们的目标就是添加不同的图片到当前编辑器,并对这些图片进行拖拽、旋转、缩放等操作。

此时在容器下添加一个 div 用于存放对应的操作元素,此时 body 下的html 代码如下:

html 复制代码
<div class="root">
    <div class="container" data-croffle-ref="element$0">
        <div class="target" data-croffle-ref="targetRef">Target</div>
    </div>
</div>

以上代码中,样式修饰为 class="target" 的 div 元素则为我们的操作元素,并且这个元素由于 moveable 的机制,给与了 data-croffle-ref="targetRef" 的自定义属性,并且这个 div 的内容为 Target 文本。

1.3 css 修饰

其实也不需要修饰,但是没有样式的话可能读者会觉得很奇怪,读者可以将 css 删除查看效果,最后发现还是加上 css 的为好(示例摘抄于 storybook 但做了精简和增加了便于文章讲解的额外内容)。

css 直接复制在 head 上即可,在此处已经给与了 style 标签:

css 复制代码
<style>
.root {
    position: relative;
}

.container {
    position: relative;
    margin-top: 50px;
}

.target {
    position: absolute;
    width: 100px;
    height: 100px;
    top: 150px;
    left: 100px;
    line-height: 100px;
    text-align: center;
    background: #ee8;
    color: #333;
    font-weight: bold;
    border: 1px solid #333;
    box-sizing: border-box;
}
</style>

以上标签中,给与了 root 根元素与容器 root 的定位方式为相对定位,接着给与了操作元素 target 的样式为一个黄色的矩形框。

css 的话我不做多的解释,其实这是布局设计问题,咱们若不是前端就直接看详细的功能实现即可,若是前端页不用说对吧,如果不懂的直接复制即可,毕竟这是个样式,没有设计到特效动效制作。

接下来 cdn 引入后会给出这一部分的代码:

1.4 cdn 引入

以下是 moveable 的 cdn:

html 复制代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/moveable/0.53.0/moveable.min.js"
        integrity="sha512-gFIuV9WCEJeWYkY1ZdJXugypot9ooEtwJf6U8In5JR6z5ZvV1xAvAQe9mQ7IYBXiF9ICXyiCeqgCJzqf64wh7A=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>

如果 cdn 引入有问题,可以在 https://cdnjs.com/libraries/moveable 查看。

此处我也给予 moveable.min.js 的下载地址:TODO

此时的代码(除 js 外应该是这样):

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>csdn 1_bit moveable how to use</title>
    <style>
        .root {
            position: relative;
        }

        .container {
            position: relative;
            margin-top: 50px;
        }

        .target {
            position: absolute;
            width: 100px;
            height: 100px;
            top: 150px;
            left: 100px;
            line-height: 100px;
            text-align: center;
            background: #ee8;
            color: #333;
            font-weight: bold;
            border: 1px solid #333;
            box-sizing: border-box;
        }
    </style>
</head>

<body>
    <div class="root">
        <div class="container" data-croffle-ref="element$0">
            <div class="target" data-croffle-ref="targetRef">Target</div>
        </div>
    </div>
</body>

</html>

1.5 js 实现元素可移动

在 moveable 中,若想让一个元素可移动其实很简单,在此我先列出代码:

javascript 复制代码
const element$0 = document.querySelector(`[data-croffle-ref="element$0"]`);
const targetRef = document.querySelector(`[data-croffle-ref="targetRef"]`);

const moveable$0 = new Moveable(
	element$0, 
	{
	    target: targetRef,
	    draggable: true,
	    throttleDrag: 1,
	    edgeDraggable: false,
	    startDragRotate: 0,
	    throttleDragRotate: 0
	}
);

moveable$0.on("drag", e => {
    e.target.style.transform = e.transform;
});

首先我们看最开始的两行,这两行都是通过 document.querySelector 寻找元素,其中一个元素是[data-croffle-ref="element$0"] 为容器元素,另一个为 [data-croffle-ref="targetRef"] 为操作元素。

容器元素与操作元素的获取这两者是必不可少的,通过确定这俩者从而进行控制。

之前也说过 document.querySelector 与自定义元素搭配即可精确找到元素,在此列出代码后不再赘述了。

在找到元素后,直接 new 一个 Moveable,其中第一个参数传入的是在 html 中定义的 element$0;第二个参数则是操作参数,则你需要如何操作这个元素。

第二个参数为一个字典,在字典中指定了操作的元素 targettargetRef,当前全部 key 键的解释如下:

  • target 操作的目标元素
  • draggable 目标元素是否可拖动
  • throttleDrag 拖动的延时毫秒数,设置为 0 则表示实时更新
  • edgeDraggable 表示目标的边缘是否可拖动
  • startDragRotate 当鼠标旋转多少后才使元素进行转动
  • throttleDragRotate 旋转的延时毫秒数,设置为 0 则表示实时更新

你可能在思考,当前操作只是移动目标元素,并没有进行旋转等操作,为什么会有这些参数。

其实当前示例时摘抄于 storybook 之上,在此列出为方便 查看 storybook 演示的读者作为解释,当前示例并不需要那么多的参数,只需要传入如下字典即可实现拖拽操作:

javascript 复制代码
{
    target: targetRef,//目标元素
    draggable: true//是否可拖拽
}

那么最后的代码:

javascript 复制代码
moveable$0.on("drag", e => {
    e.target.style.transform = e.transform;
});

则表示为 Moveable 实例的 drag 事件添加一个事件监听器,当目标元素被拖动时,会触发这个事件监听器;其中代码为 e.target.style.transform = e.transform; 则表示事件对象 e 包含了拖动操作的相关信息,其中 e.transform 是拖动后的变换样式,将 e.transform 的值赋给目标元素的 style.transform 属性,更新目标元素的位置。

此时当前拖拽的所有代码如下(省略了 style 样式,直接复制在 1.3 中的 css 即可):

javascript 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>csdn 1_bit moveable how to use</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/moveable/0.53.0/moveable.min.js"
        integrity="sha512-gFIuV9WCEJeWYkY1ZdJXugypot9ooEtwJf6U8In5JR6z5ZvV1xAvAQe9mQ7IYBXiF9ICXyiCeqgCJzqf64wh7A=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>

<body>
    <div class="root">
        <div class="container" data-croffle-ref="element$0">
            <div class="target" data-croffle-ref="targetRef">Target</div>
        </div>
    </div>
    <script>
        const element$0 = document.querySelector(`[data-croffle-ref="element$0"]`);
        const targetRef = document.querySelector(`[data-croffle-ref="targetRef"]`);
        const moveable$0 = new Moveable(element$0, {
            target: targetRef,
            draggable: true
        });

        moveable$0.on("drag", e => {
            e.target.style.transform = e.transform;
        });
    </script>
</body>

</html>

此时效果如下:

你可能会想,如果咱们可是使用的是图片,并不是文字,不要担心,图片他来了。

1.6 图片拖拽

此时在 html 中,操作元素是通过一个容器进行包裹的,既然是容器,那么是包裹整个操作元素的,那么此时我们只需要将操作元素下添加一个图片即可。

例如在操作元素下添加一个 img 标签:

html 复制代码
<div class="target" data-croffle-ref="targetRef">
    <img src="1.jpg" alt="" style="width: 100px;height: 100px;">
</div>

此时我给与了这个 img 的样式修饰 style="width: 100px;height: 100px;",此时的样式修饰是为了满足操作元素的大小,否则将会超出(虽然不会影响当前的功能,但美观上接收不了,之后将会用更加"优雅"的方式解决这个问题)。

图片各位自己放到自己的路径下即可,在此不在赘述,此时的结果如下:

2.缩放

注意:以下 html 代码与 第 1 小点 相同

使用 moveable 还可以对添加的图片进行缩放,只需要 new 一个Moveable 时传入不同的参数即可:

js 复制代码
const moveable$0 = new Moveable(element$0, {
    target: targetRef,
    scalable: true,// 启用缩放功能,设置为 true 表示允许对目标元素进行缩放操作
    keepRatio: false,// 将 keepRatio 变量的值赋给配置对象的 keepRatio 属性,控制缩放时是否保持宽高比
    renderDirections: renderDirections
});

其中参数定义如下:

  • scalable 启用缩放功能,设置为 true 表示允许对目标元素进行缩放操作
  • keepRatio 控制缩放时是否保持宽高比
  • false为不保持 renderDirections 控制显示缩放的控制点

其中 scalable 与 keepRatio 都比较好理解,只有 renderDirections 较为陌生。

renderDirections 是一个 list,这个list 存储了要显示控制点的内容,例如这个 renderDirections 的代码为 const renderDirections = ["n", "s"]; 则表示在北部和南部有一个控制点,即如下图所示:

此时具体 js 代码部分如下:

javascript 复制代码
const renderDirections = ["n", "s"];
const element$0 = document.querySelector(`[data-croffle-ref="element$0"]`);
const targetRef = document.querySelector(`[data-croffle-ref="targetRef"]`);
const moveable$0 = new Moveable(element$0, {
    target: targetRef,
    scalable: true,// 启用缩放功能,设置为 true 表示允许对目标元素进行缩放操作
    keepRatio: false,// 控制缩放时是否保持宽高比
    renderDirections: renderDirections
});


moveable$0.on("scale", e => {
    e.target.style.transform = e.drag.transform;
});

其中代码:

javascript 复制代码
moveable$0.on("scale", e => {
    e.target.style.transform = e.drag.transform;
});

为表示监听 scale 缩放事件,通过 e.drag.transform 得到最新的样式信息给予到目标元素即可改变以及重新绘制。

此时还可以更改 renderDirections 为 const renderDirections = ["nw","n","ne","w","e","sw","s","se"];,其中 nw 表示东南、ne 表示西南、以此类推运行代码后展现如下:

其展示效果如下:

若将 keepRatio: false,// 控制缩放时是否保持宽高比 改成 true,效果如下:

3.旋转

设定目标是否可进行旋转也只是需要 new 一个 moveable 时传入的参数即可:

javascript 复制代码
const moveable$0 = new Moveable(element$0, {
    target: targetRef,
    rotatable: true,
    rotationPosition: rotationPosition
});

参数说明如下:

  • rotatable 控制目标元素是否可旋转 true 为允许
  • rotationPosition 指定旋转控制点的位置

以上代码中 rotationPosition 设置为字符串类型的 val 为 "top" 即可,即:const rotationPosition = "top";

之后为 moveable$0 设置 rotate 事件监听:

javascript 复制代码
moveable$0.on("rotate", e => {
    e.target.style.transform = e.drag.transform;
});

那么即可设置目标元素的响应。

完整的 js 代码如下:

javascript 复制代码
const rotationPosition = "top";
const element$0 = document.querySelector(`[data-croffle-ref="element$0"]`);
const targetRef = document.querySelector(`[data-croffle-ref="targetRef"]`);
const moveable$0 = new Moveable(element$0, {
    target: targetRef,
    rotatable: true,// 控制目标元素是否可旋转
    rotationPosition: rotationPosition// 指定旋转控制点的位置
});
moveable$0.on("rotate", e => {
    e.target.style.transform = e.drag.transform;
});

此时的 html 代码并不需要修改,此时页面所展示的内容如下:

此时的控制点在顶部,若设置 rotationPosition 为 bottom const rotationPosition = "bottom"; 时页面展示效果如下:

此时的操作效果如下:

4.裁剪

在 moveable 中,一般情况下,只需要在 new moveable 时传入不同的参数即可对操作目标开启不同的操作,最后再为其添加对应操作的事件响应,即可完成对开启的操作完成监听。

以下是一个完成裁剪功能的 moveable 代码:

javascript 复制代码
const moveable$0 = new Moveable(element$0, {
    target: targetRef,
    draggable: true,
    clippable: true,
    clipTargetBounds: false,
});

以下为各个新增参数的解释:

  • draggable 控制目标元素是否可拖动
  • clippable 控制目标元素是否可裁剪
  • clipRelative 控制裁剪区域的定位方式
  • clipTargetBounds 控制裁剪区域是否受目标元素边界限制

此时读者可能发现,以上再 new moveable 时允许目标可拖动以及目标可剪切,那我该怎么样监听两个事件呢?其实很简单,只需要使用 on 监听两个事件即可,即:

javascript 复制代码
moveable$0.on("drag", e => {
    e.target.style.transform = e.transform;
});
moveable$0.on("clip", e => {
    e.target.style.clipPath = e.clipStyle;
});

再 drag 时给与 e.transform 到 e.target.style.transform 的 transform 即可,再 clip 时给与 e.clipStyle 到 e.target.style.clipPath 即可。

接下来我们继续回到参数之中,其中 clipTargetBounds 表示在裁剪时会不会超出区域了还可以裁剪,当设置为 true 时不允许过边界(蓝框裁剪),结果如下:

如果设置为 false则允许过边界,效果如下(也演示了拖拽效果):

此时完整的js 代码如下(html 不需要进行修改):

javascript 复制代码
const element$0 = document.querySelector(`[data-croffle-ref="element$0"]`);
const targetRef = document.querySelector(`[data-croffle-ref="targetRef"]`);

const moveable$0 = new Moveable(element$0, {
    target: targetRef,
    draggable: true,
    clippable: true,
    clipTargetBounds: false,
});

moveable$0.on("drag", e => {
    e.target.style.transform = e.transform;
});

moveable$0.on("clip", e => {
    e.target.style.clipPath = e.clipStyle;
});
相关推荐
_Kayo_39 分钟前
CSS BFC
前端·css
二哈喇子!2 小时前
Vue3 组合式API
前端·javascript·vue.js
时空自由民.2 小时前
rk3588s vscode索引失败的问题
ide·vscode·编辑器
二哈喇子!3 小时前
Vue 组件化开发
前端·javascript·vue.js
chxii3 小时前
2.9 插槽
前端·javascript·vue.js
姑苏洛言4 小时前
扫码点餐小程序产品需求分析与功能梳理
前端·javascript·后端
Freedom风间4 小时前
前端必学-完美组件封装原则
前端·javascript·设计模式
江城开朗的豌豆4 小时前
React表单控制秘籍:受控组件这样玩就对了!
前端·javascript·react.js
一枚前端小能手5 小时前
📋 代码片段管理大师 - 5个让你的代码复用率翻倍的管理技巧
前端·javascript
国家不保护废物5 小时前
Web Worker 多线程魔法:告别卡顿,轻松实现图片压缩!😎
前端·javascript·面试