低代码必备!带标尺和缩略图的画板

简介: 低代码平台经常要用到标尺和缩略图操作同步的画板,点进来就能定制你的专属低代码画板!还等什么!快看我!

最近重构某项目的仪表板,代码屎山一样没眼看!果断弃之!从零开始!自己搞更香!

1.功能需求

  1. 左侧和顶部有刻度标尺,跟着画板滚动而变化
  2. 缩略图,可以通过移动视图框来控制画板滚动
  3. 画板可放缩,对应标尺和缩略图比例跟着变
  4. 移动画板位置,对应标尺和缩略图位置跟着变
  5. 适应屏幕大小和实际大小

2. 用canvas画刻度标尺

2.1 横向刻度标尺

js

const canvas=document.getElementById('myCanvas')const padding=2;//边距const startLen=60;//开始间隔const width=500,height=24;//长宽const ctx  = canvas.getContext('2d');  const unit = 10; //间隔刻度单位    //计算出要绘制多少个刻度  const scaleCount = Math.ceil(width + startLen / unit);    /***--执行绘制---***/       canvas.width = width + startLen;    canvas.height = height;    ctx.clearRect(0, 0, width, height);    ctx.beginPath();    //绘制起点    ctx.strokeStyle = 'rgb(0, 0, 0)';    ctx.font = '10px Arial';    ctx.lineWidth = 1;    ctx.moveTo(startLen, 0);    ctx.lineTo(startLen, height);    ctx.fillText('0', startLen + padding, 13);    for (let i = 1; i <= scaleCount; i++) {    //计算每个刻度的位置      const step = startLen + Math.round(i * unit);      //10的倍数刻度大长度      if (i % 10 === 0) {        ctx.moveTo(step, 0);        ctx.lineTo(step, height);        //标注刻度值        const scaleText = i * unit + '';        ctx.fillText(scaleText, step + padding, 13);      } elseif (i % 5 === 0) {//5的倍数刻度中长度        ctx.moveTo(step, 15);        ctx.lineTo(step, height);        //标注刻度值        const scaleText = i * unit + '';        ctx.fillText(scaleText, step + padding, 13);      } else {//其他刻度小长度        ctx.moveTo(step, height - 3);        ctx.lineTo(step, height);      }    }    ctx.stroke();

2.2 纵向刻度标尺

横向和纵向的计算公式相似,但坐标计算有所不同

js

 canvas.width = height;    canvas.height = width + startLen;    ctx.clearRect(0, 0, height, width + startLen);    ctx.beginPath();    //绘制起点    ctx.strokeStyle = 'rgb(0, 0, 0)';    ctx.font = '10px Arial';    ctx.lineWidth =1;    ctx.moveTo(0, startLen);    ctx.lineTo(height, startLen);    ctx.fillText('0', padding, startLen - padding);       for (let i = 1; i <= scaleCount; i++) {     //计算每个刻度的位置      const step = startLen + Math.round(i * unit );        //10的倍数刻度大长度      if (i % 10 === 0) {        ctx.moveTo(0, step);        ctx.lineTo(height, step);        //标注刻度值        const scaleText = unit * i + '';        ctx.fillText(scaleText, padding, step - padding);      } elseif (i % 5 === 0) {//5的倍数刻度中长度        ctx.moveTo(15, step);        ctx.lineTo(height, step);        //标注刻度值        const scaleText = unit * i + '';        ctx.fillText(scaleText, padding, step - padding);      } else {//其他刻度小长度        ctx.moveTo(height - 3, step);        ctx.lineTo(height, step);      }    }    ctx.stroke();

3. 缩略图

缩略图主要由屏幕大小缩略图和可视范围缩略图组成,可视范围缩略图要对应画布移动

js

const canvas = document.getElementById('myCanvas');      const ctx = canvas.getContext('2d');      const startLen = 6;        //屏幕大小      const screenWidth = 1920;      const screenHeight = 1080;      const thumbnailSize = 0.1; //10:1的缩放比例      //缩略图大小      const canvasConfig = {        thumbnailWidth: Math.ceil(screenWidth * thumbnailSize),        thumbnailHeight: Math.ceil(screenHeight * thumbnailSize),        thumbnailWrapWidth: Math.ceil((screenWidth + 400) * thumbnailSize),        thumbnailWrapHeight: Math.ceil((screenHeight + 400) * thumbnailSize)      };      //可视范围框      const viewBox = {        viewWidth: Math.ceil(1000 * thumbnailSize),        viewHeight: Math.ceil(800 * thumbnailSize)      };      //滚动坐标      const scroll = {        scrollLeft: Math.ceil(300 * thumbnailSize),        scrollTop: Math.ceil(200 * thumbnailSize)      };      //计算出要绘制多少个刻度      canvas.width = canvasConfig.thumbnailWrapWidth;      canvas.height = canvasConfig.thumbnailWrapHeight;      //画缩略框      ctx.clearRect(0, 0, canvasConfig.thumbnailWrapWidth, canvasConfig.thumbnailWrapHeight);      ctx.beginPath();      ctx.fillStyle = 'rgba(26, 103, 255, 0.5)';      ctx.rect(startLen, startLen, canvasConfig.thumbnailWidth, canvasConfig.thumbnailHeight);      ctx.fill();      //画可视范围框      ctx.beginPath();      ctx.strokeStyle = '#1a67ff';      ctx.rect(        Math.round(scroll.scrollLeft),        Math.round(scroll.scrollTop),        viewBox.viewWidth,        viewBox.viewHeight      );      ctx.stroke();

4. 画板

画板样式

scss

  .canvas-panel-wrap {    position: absolute;    box-shadow: var(--canvas-shadow) 0030px0;    transform-origin: left top;    margin-left: 60px;    margin-top: 60px;  }

画板大小

js

//操作空间大小,预留400px作为移动showWidth() {      returnthis.screenWidth * (this.scale < 100 ? 1 : this.percent) + 400;    },    showHeight() {      returnthis.screenHeight * (this.scale < 100 ? 1 : this.percent) + 400;    },   canvasStyle: computed(() => ({          left: -scrollLeft.value + 'px',          top: -scrollTop.value + 'px',          width: editorStore.screenWidth + 'px',          height: editorStore.screenHeight + 'px',          transform: `scale(${editorStore.scale * 0.01})`        })),

5.画板缩放,标尺和缩略图同步

scale 缩放比例范围[20-200]

5.1 标尺跟随缩放

单位刻度长度,标尺刻度都需添加缩放值,重新计算,这样才能让标尺像素保持不变的情况下,刻度值对应上画板的大小

注意不可以用transform:scale来缩放标尺,会导致像素模糊问题

js

const percent = scale * 0.01;//单位刻度长度  let unit = Math.ceil(10 / percent);  if (unit < 8) {    unit = 8;  }  //计算出要绘制多少个刻度  const scaleCount = Math.ceil(width + startLen / unit);   //……       //计算每个刻度的位置        const step = startLen + Math.round(i * unit * percent);  //……

200%的标尺

70%的标尺

20%的标尺

可以看到开始的间隔0刻度开始的地方是不变的,这就是像素不变,但刻度对应

5.2 缩略图跟随缩放

缩略图不可固定比例,当缩放值大于100时会导致整张缩略图跟着变大,占据操作空间,而当缩放值小于100时则会导致整张缩略图跟着变小,不好操作,因此需要处理一下;让缩略图保持大小。

js

//缩略图比例thumbnailSize() {      if (this.scale > 100) {        return10 / this.scale;      } else {        return0.1;      }    },    //缩略图大小canvasConfig() {      return {        thumbnailWidth: Math.ceil(this.screenWidth * this.thumbnailSize * this.percent),        thumbnailHeight: Math.ceil(this.screenHeight * this.thumbnailSize * this.percent),        thumbnailWrapWidth: Math.ceil(this.showWidth * this.thumbnailSize),        thumbnailWrapHeight: Math.ceil(this.showHeight * this.thumbnailSize)      };    }

6. 移动画板

6.1 标尺跟随移动

按空格键切换显隐画布操作蒙版,通过移动蒙版来实现移动画布,通过滚轮来缩放画布

js

constonKeyAction = (e: KeyboardEvent) => {//按空格键切换显隐操作操作蒙版        if (e.keyCode == keyCode.space) {          editorStore.setMoveCanvas(!editorStore.isMoveCanvas);        }      };      //滚轮缩放画布      constonWheelAction = (e: WheelEvent) => {        if (isMoveCanvas.value) {          if (e.wheelDelta > 0) {            editorStore.setScale(editorStore.scale + 1);          } else {            editorStore.setScale(editorStore.scale - 1);          }        }      };      //注册监听动作      onMounted(() => {        window.addEventListener('keydown', onKeyAction);        window.addEventListener('wheel', onWheelAction);        refreshRuler();      });      //取消监听动作      onBeforeUnmount(() => {        window.removeEventListener('keydown', onKeyAction);        window.removeEventListener('wheel', onWheelAction);      });      

移动蒙版

js

//移动画布信息      let moveInfo = {        startX: 0,        startY: 0      };//记录开始位置onMoveCanvasDown: (e: MouseEvent) => {          e.stopPropagation();          moveInfo = {            startX: e.clientX,            startY: e.clientY          };        },        //结束鼠标操作后移动画布        onMoveCanvasUp: (e: MouseEvent) => {          e.stopPropagation();          //计算移动坐标          let left = scrollLeft.value - (e.clientX - moveInfo.startX);          let top = scrollTop.value - (e.clientY - moveInfo.startY);          //   console.log('move', left, top);          editorStore.setScroll({ left, top });        }

移动范围有效性校验,以免移动出界

js

    setScroll({ left, top }: { left: number; top: number }) {      const distance = 60;      if (left < 0) {        left = 0;      } elseif (left > this.showWidth - this.viewWidth - distance) {        left = this.showWidth - this.viewWidth - distance;      }      if (top < 0) {        top = 0;      } elseif (top > this.showHeight - this.viewHeight - distance) {        top = this.showHeight - this.viewHeight - distance;      }      this.$state.scrollLeft = Math.round(left);      this.$state.scrollTop = Math.round(top);    },

6.2 缩略图跟随移动

缩略图可视范围框是当前仪表板给的内容空间,移动这个可视范围框根据对应反比例,可映射到整个画布的移动

另外,需要监听画布大小和移动更新缩略图和可视范围位置,监听window的resize动作,更新可视范围

js

//可视范围      let dashboardDom = document.getElementById('dashboard');          if (!dashboardDom) {            return;          }          viewBox.value.viewWidth = dashboardDom.offsetWidth;          viewBox.value.viewHeight = dashboardDom.offsetHeight;//缩略图反比例,对应上缩略图比例的计算const unscale = computed(() => {        if (editorStore.scale > 100) {          return1 / editorStore.thumbnailSize;        } else {          return10;        }      });     

移动缩略图中可视范围

js

let moveInfo = {        startX: 0,        startY: 0,        isMove: false      };      //记录开始位置         onMoveStart: (e: MouseEvent) => {          moveInfo.isMove = true;          moveInfo.startX = e.clientX;          moveInfo.startY = e.clientY;        },                onMove: (e: MouseEvent) => {          if (moveInfo.isMove) {          //计算反比例移动坐标            let left = editorStore.scrollLeft + (e.clientX - moveInfo.startX) * unscale.value;            let top = editorStore.scrollTop + (e.clientY - moveInfo.startY) * unscale.value;                        editorStore.setScroll({ left, top });            moveInfo.startX = e.clientX;            moveInfo.startY = e.clientY;          }        },        //结束移动        onMoveEnd: () => {          moveInfo.isMove = false;        }

7. 大小适配可视范围

自适应比例 = (可视范围高度-边距和标尺高度/屏幕高度)*100%

js

constonFitCanvas = () => {        store.setScale(          parseInt(          ((document.getElementById('dashboard').offsetHeight - 84) / store.screenHeight) * 100          )        );      };

总结

其实,标尺和缩略图操作协同的画板并不难,主要是缩放比例计算和视图转换的问题!啦啦啦!现在你已经学会了!可以拥有一个常用的低代码画板了!

src/assets/vars.scss这里抽离了一些颜色值,方便大家配置自己想要的样式,canvas绘制的颜色值还是得手动赋上去.

css

:root{  //标尺背景颜色  --ruler-bg: #f4f7fe;  //移动蒙版背景颜色  --move-bg: rgba(0, 0, 0, 0.1);  //画布阴影  --canvas-shadow: rgba(0, 0, 0, 0.5);//右下小操作栏  --slider-icon: #32363c;  --slider-bg: #ffffff;  --canvas-slider-border: rgba(0, 0, 0, 0.1);  --thumbnail-wrap-bg: #f4f7fe;  }

GitHub地址

https://github.com/xiaolidan00/ruler-canvas

相关文章
|
15天前
ThreeJs添加拖动辅助线
这篇文章介绍了在Three.js中使用TransformControls组件来添加拖动辅助线,实现对3D模型在不同轴向上进行直观的拖动和平移操作。
36 0
|
算法 Windows
Winform控件优化之实现无锯齿的圆角窗体(或任意图形的无锯齿丝滑的窗体或控件)【借助LayeredWindow】
在一般能搜到的所有实现圆角窗体的示例中,都有着惨不忍睹的锯齿...而借助于Layered Windows,是可以实现丝滑无锯齿效果的Form窗体的,其具体原理就是分层窗体....
1555 0
Winform控件优化之实现无锯齿的圆角窗体(或任意图形的无锯齿丝滑的窗体或控件)【借助LayeredWindow】
|
4月前
|
前端开发 数据安全/隐私保护 容器
简约渐变色登陆布局html+css代码
这是一段包含HTML和CSS代码的摘要。HTML部分定义了一个基本的网页结构,包括`&lt;html&gt;`、`&lt;head&gt;`、`&lt;body&gt;`标签,以及一个简单的登录界面,由一个容器`.container`包含一个登录框`.login-wrapper`,登录框内有输入框和登录按钮。CSS部分设置了全局样式,如字体、边距,并为HTML元素添加了样式,如背景渐变色、文字对齐、输入框和按钮的样式等。整个代码展示了创建一个具有响应式布局和特定视觉效果的简洁登录页面。
51 0
|
5月前
|
定位技术
Pyglet综合应用|推箱子游戏地图编辑器之图片跟随鼠标
Pyglet综合应用|推箱子游戏地图编辑器之图片跟随鼠标
56 0
|
定位技术
Echarts实战案例代码(39):地理坐标整体地图背景色渐变效果和字体随地图缩放的解决方案
Echarts实战案例代码(39):地理坐标整体地图背景色渐变效果和字体随地图缩放的解决方案
298 0
Photoshop绘制立体风格的拾色器图标
Photoshop绘制立体风格的拾色器图标
50 0
|
算法 前端开发 数据可视化
语雀新画板
语雀新画板
376 0
|
定位技术
【Axure教程】拖拉拽放大缩小和移动元件
【Axure教程】拖拉拽放大缩小和移动元件
SwiftUI—如何将颜色、图片和图形作为视图的背景
SwiftUI—如何将颜色、图片和图形作为视图的背景
813 0
SwiftUI—如何将颜色、图片和图形作为视图的背景