文章目录
- ⭐前言
- ⭐初始化react app
- ⭐封装拖拽
- ⭐页面雏形
- ⭐下一步计划
- ⭐结束
⭐前言
大家好,我是yma16,本文分享 前端react——从零开始的拖拽生成email(第一章)
背景
作为2025练手项目
前端系列文章
vue3 + fastapi 实现选择目录所有文件自定义上传到服务器
前端vue2、vue3去掉url路由“ # ”号——nginx配置
csdn新星计划vue3+ts+antd赛道——利用inscode搭建vue3(ts)+antd前端模板
认识vite_vue3 初始化项目到打包
python_selenuim获取csdn新星赛道选手所在城市用echarts地图显示
让大模型分析csdn文章质量 —— 提取csdn博客评论在文心一言分析评论区内容
前端vue3——html2canvas给网站截图生成宣传海报
前端——html拖拽原理
前端 富文本编辑器原理——从javascript、html、css开始入门
前端老古董execCommand——操作 选中文本 样式
前端如何在30秒内实现吸管拾色器?
前端——原生Selection api操作选中文本 样式、取消样式(解决标签的无限嵌套问题)
⭐初始化react app
创建 drag-email 目录的react app
# npx create-react-app drag-email
暴露 webpack配置,方便后续引入less
# npm run eject
webpack.config 配置加载less
// 加载less
const lessRegex = /\.less$/i;
配置添加
{test: lessRegex,use: getStyleLoaders({importLoaders: 3,sourceMap: isEnvProduction? shouldUseSourceMap: isEnvDevelopment,modules: {getLocalIdent: getCSSModuleLocalIdent,},},'less-loader'),
}
⭐封装拖拽
主要针对当前元素和目标元素添加drag和drop事件。
interface ConfigType {source: any;target: any;
}
class DragOption {public sourceDom = null;public targetDom = null;public dragConfig = {curentDrag: null,};constructor(config: { source: any }) {const { source } = config;this.sourceDom = source;this.addListen();}// 初始化public addListen() {this.dragStart();this.drag();this.dragend();}public removeListen() {this.removeDragStart();this.removeDrag();this.removeDragend();}/* 在可拖动的目标上触发的事件 */public dragStart() {// @ts-ignorethis.sourceDom.addEventListener("dragstart", (event: any) => {// 保存被拖动元素的引用 draggingthis.dragConfig.curentDrag = event.target;// 拖拽 样式 draggingevent.target.classList.add("dragging");});}public removeDragStart() {// @ts-ignorethis.sourceDom.removeEventListener("dragstart", (event: any) => {// 保存被拖动元素的引用 draggingthis.dragConfig.curentDrag = event.target;// 拖拽 样式 draggingevent.target.classList.add("dragging");});}public drag() {// @ts-ignorethis.sourceDom.addEventListener("drag", (event: any) => {// 拖拽中console.log("dragging");});}public removeDrag() {// @ts-ignorethis.sourceDom.removeEventListener("drag", (event: any) => {// 拖拽中console.log("dragging");});}public dragend() {// @ts-ignorethis.sourceDom.addEventListener("dragend", (event: any) => {// 在可拖动元素进入潜在的放置目标时高亮显示该目标if (event.target.classList.contains("dropzone")) {event.target.classList.add("dragover");}});}public removeDragend() {// @ts-ignorethis.sourceDom.removeEventListener("dragend", (event: any) => {// 在可拖动元素进入潜在的放置目标时高亮显示该目标if (event.target.classList.contains("dropzone")) {event.target.classList.add("dragover");}});}
}class DropOption {public targetDom = null;public dragConfig = {curentDrag: null,};constructor(config: { target: any }) {const { target } = config;this.targetDom = target;this.addListen();}// 初始化public addListen() {this.dragOver();this.dragenter();this.dragleave();this.drop();}// 去掉监听public removeListen() {this.removeDragOver();this.removeDragenter();this.removeDragleave();this.removeDrop();}/* 在放置目标上触发的事件 */public dragOver() {// @ts-ignorethis.targetDom.addEventListener("dragover",(event: any) => {// 阻止默认行为以允许放置event.preventDefault();},false);}public removeDragOver() {// @ts-ignorethis.targetDom.removeEventListener("dragover",(event: any) => {// 阻止默认行为以允许放置event.preventDefault();},false);}public dragenter() {// @ts-ignorethis.targetDom.addEventListener("dragenter", (event: any) => {// 拖动结束,去掉draggingevent.target.classList.remove("dragging");});}public removeDragenter() {// @ts-ignorethis.targetDom.removeEventListener("dragenter", (event: any) => {// 拖动结束,去掉draggingevent.target.classList.remove("dragging");});}public dragleave() {// @ts-ignorethis.targetDom.addEventListener("dragleave", (event: any) => {// 在可拖动元素离开潜在放置目标元素时重置该目标的背景if (event.target.classList.contains("dropzone")) {event.target.classList.remove("dragover");}});}public removeDragleave() {// @ts-ignorethis.targetDom.removeEventListener("dragleave", (event: any) => {// 在可拖动元素离开潜在放置目标元素时重置该目标的背景if (event.target.classList.contains("dropzone")) {event.target.classList.remove("dragover");}});}public dropEvent(event: any) {// 阻止默认行为(会作为某些元素的链接打开)event.preventDefault();console.log("add before");// 将被拖动元素移动到选定的目标元素中if (event.target.classList.contains("dropzone")) {event.target.classList.remove("dragover");// 删除自身// config.draged.parentNode.removeChild(config.draged);// event.target.appendChild(config.draged);// 创建虚拟dom 组件自定义的地方const vDom = document.createElement("div");console.log(vDom, "vDom");vDom.classList.add("container-box-left-component");event.target.appendChild(vDom);console.log("add component");}}public drop() {console.log("add drop listen");//@ts-ignorethis.targetDom.addEventListener("drop", this.dropEvent);}public removeDrop() {console.log("removeEventListener drop listen");//@ts-ignorethis.targetDom.removeEventListener("drop", this.dropEvent);}
}export { DragOption, DropOption };
⭐页面雏形
页面采用简单的左右布局
import "./App.css";
import { useEffect, useRef, useMemo } from "react";import { DragOption, DropOption } from "./config/const";function App() {const sourceRef = useRef(null);const targetRef = useRef(null);const componentOptions = [{name: "文字",type: "text",content: "输入文字",id: "1",},{name: "图片",type: "image",content: "图片",id: "2",},{name: "列",type: "column",content: "",id: "3",},{name: "分割线",type: "line",content: "",id: "4",},{name: "按钮",type: "button",content: "按钮",id: "5",},];const onClear = () => {if (targetRef.current) {// @ts-ignoretargetRef.current.innerHTML = "";}};useMemo(() => {console.log("componentOptions", componentOptions);}, [componentOptions]);useEffect(() => {let sourceClassArr: any = [];if (sourceRef.current) {componentOptions.map((item) => {const source = document.getElementById(item.id);const dragOption = {source,};const DragClass = new DragOption(dragOption);console.log(DragClass);sourceClassArr.push(DragClass);});}return () => {sourceClassArr.forEach((item: any) => {item.removeListen();item = null;});console.log("卸载 sourceRef");};}, [sourceRef]);useEffect(() => {let dropClass: any = null;if (targetRef.current) {dropClass = new DropOption({target: targetRef.current,});console.log("dropClass");}return () => {dropClass?.removeListen();dropClass = null;console.log("卸载 targetRef");};}, [targetRef]);// @ts-ignorereturn (<div className="container"><div className="container-header"><button className="base-button">导出 邮件html</button><button className="base-button" onClick={onClear}>清空</button></div><div className="container-box"><div className="container-box-left" ref={sourceRef}>{componentOptions.map((item) => {return (<divclassName="container-box-left-component basic-component"draggable="true"id={item.id}key={item.id}>{item.name}</div>);})}</div><div className="container-box-right"><divclassName="container-box-right-box dropzone"id="droptarget"ref={targetRef}>{/* 可以拖到这里 */}</div></div></div></div>);
}export default App;
页面基本布局
⭐下一步计划
- 引入ui框架美化样式
- 组件可编辑(文字输入、图片上传、组件padding设置)
- 富文本编辑选中文本的样式放到下周实现 2.15 号左右
⭐结束
本文分享到这结束,如有错误或者不足之处欢迎指出!
👍 点赞,是我创作的动力!
⭐️ 收藏,是我努力的方向!
✏️ 评论,是我进步的财富!
💖 感谢你的阅读!