从张鑫旭的demo中,我学到了图像拉伸的原理

news2024/9/22 12:46:34

文章收录:

  • 个人网址:http://linglan01.cn/
  • Github仓库:https://github.com/CatsAndMice/blog/issues

产品经理又有新需求啦,其中有一个图片上传后用户拉伸图像宽高的功能,评估后因要卡上线时间来不及砍掉了。保不准下一个版本又会提这个功能,所以还是要去研究研究。

幸亏我有关注张鑫旭大佬的博客,印象中记得发表过一篇关于图像拉伸的文章,就是它JS之我用单img元素实现了图像resize拉伸效果。刚好满足产品想要的效果,demo都是现成的。

文章对js逻辑部分并没有描述,像我这种爱学习,那不得知其所以然。

因此,我读了读源码200行左右,并且去掉边界判断逻辑,只将核心逻辑写了一遍。

先把效果秀出来:

先搞定图像拉伸样式

先写一个img元素,给它的src属性添加一个在线的图像链接。

<img class="image-size" src="https://pic1.zhimg.com/v2-d58ce10bf4e01f5086c604a9cfed29f3_r.jpg?source=1940ef5c" alt="拉伸">

再给它整点样式重点是 border-image属性,大佬文章也是介绍使用border-image属性做到单img实现拉伸。不赘述,跟我一样爱学习的人肯定会去瞅一眼大佬文章的。

/* 先默认宽度400px */ 
.image-size {
  width: 400px;
}

img.active {
  cursor: default;
  z-index: 1;
  display: inline-block;
  vertical-align: bottom;
  font-size: 12px;
  border: 3px solid transparent;
  border-image: url("data:image/svg+xml,%3Csvg width='30' height='30' viewBox='0 0 30 30' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='%23914AFF' d='M2.5 2.5h25v25h-25z'/%3E%3Cpath d='M0 0v12h2V2h10V0H0zM0 30V18h2v10h10v2H0zM30 0H18v2h10v10h2V0zM30 30H18v-2h10V18h2v12z' fill='%23914AFF'/%3E%3C/svg%3E") 12 / 12px / 0;
  margin: -1px;
  position: relative;
  -webkit-user-select: none;
  user-select: none;
}

点击图片后,给它添加一个active类名。若点击的不是该图像,则清除图像的active类名。

const image = document.getElementsByClassName('image-size')[0]
image.onclick = (e) => {
  if (image.classList.contains('active')) return
  image.classList.add('active')
}

document.onclick = (e) => {
   if (e.target === image) return
   image.classList.remove('active')
}

如下GIF录屏所示:

也可以点击这里体验:拉伸样式demo

再搞定鼠标光标样式

鼠标光标默认箭头,现在需要当鼠标移动到图像左上、左下、右上、右下四个角时,鼠标光标样式随之进行改变。

注意看GIF演示中的鼠标变化:

上图是大佬的demo。

鼠标移动至左上角、右下角处,鼠标光标样式修改成:

鼠标移动至左下角、右上角,鼠标光标样式修改成:

修改鼠标光标使用属性cursor,对该属性不清楚的童鞋们移步cursor官方文档。

给图像添加active类名后,再给document绑定一个鼠标移动事件onmousemove。当鼠标移动过程中计算鼠标位置是否已进行某个区域内。

该区域可以为下图红框框起来的区域,图像左上、左下、右上、右下四个角均会有一个这样的区域。

计算过程要获取到图像的left、top、right、bottom值,也就是使用Element.getBoundingClientRect(),不清楚该API的童鞋移步getBoundingClinetRect官方文档

 //省略...
 
image.onclick = (e) => {
  if (image.classList.contains('active')) return
  image.classList.add('active')

  document.onmousemove = (e) => {
      const target = e.target
      if (target !== image || !target.classList.contains('active')) return
      const x = e.clientX,
          y = e.clientY
      const { top, left, bottom, right } = image.getBoundingClientRect()
      //左上角或右下角
      if ((bottom - y < 20 && right - x < 20) || (x - left < 20 && y - top < 20)) {
          image.style.cursor = 'nwse-resize'

          //左下角或右上角
      } else if ((y - top < 20 && right - x < 20) || (bottom - y < 20 && x - left < 20)) {
          image.style.cursor = 'nesw-resize'

          //若都不是,鼠标光标为默认箭头样式
      } else {
          image.style.cursor = 'default'
      }
  }
}

//省略...

伪代码中20这个数字是我随意写的,这个值为红框框起来区域的宽高。

若取消图像的拉伸状态,则也把document已绑定的鼠标移动事件取消。

//省略...

document.onclick = (e) => {
  if (e.target === image) return
  image.classList.remove('active')
  document.onmousemove = null
}

可以点击这里体验:图像拉伸鼠标样式改变demo。

让图像宽高动起来

先给image元素添加onmousedown事件,并获取鼠标左键按下时clientXclientY的值作为开始拉伸的位置,默认为0

拉伸逻辑计算发生在document.onmousemove事件内,因此,需要有一个变量isResizeing表示image.onmousedown事件是否被触发。触发,鼠标移动即图像宽高也要进行改变;未触发,鼠标移动仅改变鼠标光标的样式,不影响图像的宽高。

isResizeing不能一直为true,鼠标抬起onmouseup重置为false

 //省略...
 
image.onclick = (e) => {
  if (image.classList.contains('active')) return
  image.classList.add('active')
  let startX = 0,
      startY = 0,
      //是否拉伸状态
      isResizeing = false,
      position = ''
  image.onmousedown = (e) => {
      //鼠标左键落下的X、Y位置
      startX = e.clientX
      startY = e.clientY
      isResizeing = true
  }

  document.onmousemove = (e) => {
     e.preventDefault()
     const target = e.target
     let x = e.clientX,
          y = e.clientY,
          distanceX = x - startX,
          distanceY = y - startY
     if(isResizeing){
       //图像已处于拉伸的状态
     
    
     }else{
       //改变鼠标光标样式
       
       if(if (target !== image || !target.classList.contains('active')) return){
         const { top, left, bottom, right } = image.getBoundingClientRect()
          //左上角或右下角
         if ((bottom - y < 20 && right - x < 20) || (x - left < 20 && y - top < 20)) {
            image.style.cursor = 'nwse-resize'
  
            //左下角或右上角
         } else if ((y - top < 20 && right - x < 20) || (bottom - y < 20 && x - left < 20)) {
            image.style.cursor = 'nesw-resize'
  
            //若都不是,鼠标光标为默认箭头样式
         } else {
            image.style.cursor = 'default'
         }
       }
    }
  
  document.onmouseup = () => {
    //鼠标抬起时,重置isResizeing变量
    isResizeing = false
  }
}

//省略...

还需要有一个变量 position,用于判断拉伸的是左上、右下、左下、右上四个角中的哪一个 。

    //省略
    if ((bottom - y < 20 && right - x < 20) || (x - left < 20 && y - top < 20)) {
          //左上角或右下角
          
          image.style.cursor = 'nwse-resize'
          // 右下角
          if (bottom - y < 20) {
              position = 'bottom right'
              return
          }
          position = 'top left'
    
     } else if ((y - top < 20 && right - x < 20) || (bottom - y < 20 && x - left < 20)) {
       //左下角或右上角
       
        image.style.cursor = 'nesw-resize'
        // 右上角
        if (y - top < 20) {
            position = 'top right'
            return
        }

        position = 'bottom left'

        //若都不是,鼠标光标为默认箭头样式
     } else {
        image.style.cursor = 'default'
         position = ''
     }
   //省略 ...

有了position变量,即可在if(isResizeing){...}这个代码块中依据position值进行拉伸计算。

 //省略...
 
image.onclick = (e) => {
  if (image.classList.contains('active')) return
  image.classList.add('active')
  let startX = 0,
      startY = 0,
      //是否拉伸状态
      isResizeing = false,
      position = '',
      storeWidth = 0,
      storeHeight = 0
  image.onmousedown = (e) => {
      //鼠标左键落下的X、Y位置
      startX = e.clientX
      startY = e.clientY
      isResizeing = true
      storeWidth = image.clientWidth
      storeHeight = image.clientHeight
  }

  document.onmousemove = (e) => {
     e.preventDefault()
     const target = e.target
     let x = e.clientX,
        y = e.clientY,
        distanceX = x - startX,
        distanceY = y - startY,
        width = 0,
        height = 0
     if(isResizeing){
       //图像已处于拉伸的状态
          if (!position) return
          if (position === 'bottom right') {
              /*
                  右下角  
                  distanceX值为正,distanceY值为正,图像宽高变大;
                  distanceX值为负,distanceY值为负,图像宽高变小
              */
              width = storeWidth + distanceX
              height = storeHeight + distanceY
          } else if (position === 'top left') {
              /*
                  左上角正好与右下角相反
                  distanceX值为正,distanceY值为正,图像宽高变大;
                  distanceX值为负,distanceY值为负,图像宽高变小
              */
              width = storeWidth - distanceX
              height = storeHeight - distanceY
          } else if (position === 'top right') {
              /*
                  右上角
                  distanceX值为正,distanceY值为负,图像宽高变大;
                  distanceX值为负,distanceY值为正,图像宽高变小
              */
              width = storeWidth + distanceX
              height = storeHeight - distanceY
          } else if (position === 'bottom left') {
              /*
                  左下角
                  distanceX值为负,distanceY值为正,图像宽高变大;
                  distanceX值为正,distanceY值为负,图像宽高变小
              */
              width = storeWidth - distanceX
              height = storeHeight + distanceY
          }  
         image.style.width = Math.round(width) + 'px'
         image.style.height = Math.round(height) + 'px'  
     }else{
       //省略
    }
  
  document.onmouseup = () => {
    //鼠标抬起时,重置isResizeing变量
    isResizeing = false
  }
}

//省略...

该部分代码可以优化,但这样写容易理解,如何优化可以看下张鑫旭/单IMG元素的图像拉伸效果 。

完成这步,图像已经可以位伸宽高了。

如下GIF录屏所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t6qHFh4k-1682906274449)(https://linglan008-blog.oss-cn-hangzhou.aliyuncs.com/images/%E6%8B%89%E4%BC%B8%E7%A7%BB%E5%8A%A8-min.gif)]

可以点击这里体验:图像拉伸宽高改变。

图像是有比例的,如常见比例16:9,3:4等。现在实现出来的拉伸没有按比例进行拉伸,还需要优化。

先计算出图像比例,比例=图像宽度/ 图像高度。比较distanceX、distanceY两者值谁的移动距离更大。若distanceX值更大,则以图像宽度计算出它的高度;若distanceY值更大,则以图像的高度计算出它的宽度。

 //省略...
 
image.onclick = (e) => {
    //省略
  document.onmousemove = (e) => {
     e.preventDefault()
     const target = e.target
     let x = e.clientX,
        y = e.clientY,
        distanceX = x - startX,
        distanceY = y - startY,
        width = 0,
        height = 0
     if(isResizeing){
       //图像已处于拉伸的状态
          if (!position) return
          //省略
          
          let imageWidth = 0,
              imageHeight = 0 
          const ratio = storeWidth / storeHeight
          // 选择移动距离大的方向
          if (Math.abs(distanceX) > Math.abs(distanceY)) {
              // 宽度变化为主
              imageWidth = width;
              imageHeight = width / ratio;
          } else {
              // 高度变化为主
              imageHeight = height;
              imageWidth = height * ratio;
          }
         image.style.width = Math.round(imageWidth) + 'px'
         image.style.height = Math.round(imageWidth) + 'px'  
     }else{
       //省略
    }
   //省略
}

//省略...

OK,到此完毕。

如下GIF录屏所示:


可以点击这里体验:图像拉伸。

总结

从读张鑫旭的文章demo源码出发,自己也对图像拉伸的功能实现了一遍,做到脑会手也会。以后再有类似的功能分分钟搞定它!

关注张鑫旭大佬,好处多多。

如果我的文章对你有帮助,你的👍就是对我的最大支持_

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/480292.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java 基础进阶篇(四)—— 抽象类与模板方法设计模式

文章目录 一、抽象类、抽象方法概述二、抽象类的特征三、模板方法设计模式3.1使用场景3.2 实现步骤3.3 写作文案例 补充&#xff1a;final 和 abstract 是什么关系? 一、抽象类、抽象方法概述 在 Java 中 abstract 是抽象的意思&#xff0c;可以修饰类、成员方法。 abstract …

【LeetCode股票买卖系列:122. 买卖股票的最佳时机 II | 贪心 | 暴力递归=>记忆化搜索=>动态规划】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

基于 GS232 搭建的 SoC_up 说明

1.1 GS232 开源版本简介 GS232 开源版本不包含 DSP、浮点部件等。 TLB 大小为 32 项。 指令和数据 Cache 为 4 路组相连,每路大小为 4KB,Cache 行大小为 32 bytes。 对外接口为 32 位 AXI 接口。 1.2 1,soc_up结构 SoC_up 如上图所示。开源 GS232 对外有一个 AXI 接口,连…

「Codeforces」771-div2 E. Colorful Operations

E. Colorful Operations https://codeforces.com/contest/1638/problem/E 题目描述 给你一个数组&#xff0c;默认初始元素为 0 &#xff0c;颜色为 1&#xff0c;有三种操作&#xff1a; Color l r c&#xff1a;将 [l, r] 区间内的颜色修改为 cAdd c x&#xff1a;将所有颜…

Human Pose as Compositional Tokens 阅读笔记

人体姿态作为合成 token —— CVPR2023 论文链接 代码链接 摘要&#xff1a; 人体姿态常由身体关节的坐标向量或其热图embedding表示。虽然数据易于处理&#xff0c;但由于身体关节间缺乏依赖建模&#xff0c;即使是不现实的姿态也被接受。本文提出了一种结构化表示&#xff1…

el-form-renderer 使用指南

目录 前言 起步 使用 update-form && getFormValue 表单项动态显示或隐藏(hidden) 表单数据联动(on) 输入/输出格式化(inputFormat/outputFormat) set-options el-form-renderer 实践案例 案例一 案例二 自定义组件接入指南 前言 el-form-renderer是基于e…

Starting Windows PowerShell (启动 Windows PowerShell)

Starting Windows PowerShell (启动 Windows PowerShell) Windows PowerShell is a scripting engine .DLL that’s embedded into multiple hosts. The most common hosts you’ll start are the interactive command-line powershell.exe and the Interactive Scripting Envi…

【Java笔试强训 26】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;跳台阶扩…

Redis`数据结构`与`对象`概述

文章目录 Redis数据结构与对象概述一、数据结构1、简单动态字符串&#xff08;SDS&#xff09;SDS结构体定义SDS结构示意图使用SDS的五个优点 2、双端链表&#xff08;list&#xff09;链表结构体定义list结构示意图 3、字典&#xff08;dict&#xff09;字典结构体定义dict结构…

想要成为 NLP 领域的大牛?从 ChatGPT 的 5 大自然语言模型开始了解吧(LM、Transformer、GPT、RLHF、LLM)——小白也能看得懂

目录 前言ChatGPT基础科普——知其一点所以然1. LM2. Transformer3. GPT4. RLHF5. LLM 参考资料其它资料下载 前言 如果想在自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;领域内脱颖而出&#xff0c;那么你一定不能错过 ChatGPT 的 5 大自然…

软件设计师笔记

软件设计师笔记 计算机组成与体系结构 数据的表示、计算机结构、Flynn分类法、CISC与RISC、流水线技术、存储系统、总线系统、可靠性、校验码 1. 数据的表示 &#xff08;一&#xff09;进制转换 R进制转十进制使用按权展开法&#xff1a; 十进制转R进制使用短除法 二进制…

Python——狂肝两万字带你学会【类与对象】

目录 01-初始对象 生活中的数据组织 程序中的数据组织​ 使用对象组织数据 总结01 02-类的成员方法 类的定义和使用 成员变量和成员方法 成员方法的定义语法 注意事项 成员方法——代码演示 总结02 03-类和对象 现实世界的事物和类 类和对象 使用类和对象描述…

Java 基础进阶篇(三)—— 权限修饰符、final 关键字与枚举

文章目录 一、权限修饰符二、final 关键字2.1 final 作用2.2 final 修饰变量举例2.3 常量 三、枚举3.1 枚举的格式3.2 枚举的特征3.3 枚举的应用 一、权限修饰符 权限修饰符 用于约束成员变量、构造器、方法等的访问范围。 权限修饰符&#xff1a; 有四种作用范围由小到大 (p…

vue+element 多选级联选择器自定义props

前言 我这里分享的是Cascader 级联选择器中的多选、以及如何自定义props的使用详解 1.使用Cascader 级联选择器 效果 代码 <div class"block"><span class"demonstration">默认显示所有Tag</span><el-cascader:options"op…

Vue电商项目--vuex模块开发

vuex状态管理库 vuex是什么&#xff1f; vuex是官方提供的一个插件&#xff0c;状态管理库&#xff0c;集中式管理项目中组件共有的数据。 切记&#xff0c;并不是全部的项目都需要Vuex,如果项目很小&#xff0c;完全不需要vuex,如果项目很大&#xff0c;组件很多&#xff0…

一道Python初学者常犯错误解析

1. 引言 在Python学习中&#xff0c;经常会遇到各种各样的代码错误&#xff0c;尤其对于初学者而言&#xff0c;明明觉得逻辑上是对的&#xff0c;但是代码运行起来&#xff0c;往往不是自己想要的结果。 本文就最近在某平台看到的一个常见错误进行展开&#xff0c;帮助大家更…

06 虚拟化Open vSwitch环境部署

文章目录 06 虚拟化Open vSwitch环境部署6.1 安装Open vSwitch网桥6.1.1 安装Open vSwitch组件6.1.1.1 安装Open vSwitch组件6.1.1.2 启动Open vSwitch服务6.1.1.3 设置Open vSwitch服务随系统自动启动 6.1.2 确认安装是否成功6.1.2.1确认 Open vSwitch组件是否安装成功6.1.2.2…

kill 信号

kill -0 PidNum 参数是0&#xff0c;不会发送任何的信号&#xff0c;不会关闭程序&#xff0c;但会执行错误检查&#xff0c;对程序运行状态进行监控。可以用他来检测某个进程ID或进程组ID是否存在。从理解上看&#xff0c;作用相当于ps -p 。 进程已停止、不存在或其他异…

前端小白是如何利用chatgt用一周时间从做一款微信小程序的

前端小白是如何利用chatgt用一周时间从0做一款微信小程序的 随着chatgpt的大火&#xff0c;真的是在工作上给各行各业的人带来了极大的便利&#xff0c;本人是一个java程序员&#xff0c;其实我自己是一直想开发一款属于自己的小程序的&#xff0c;但是迫于对前端知识的贫瘠&a…

【五一创作】VimPlug插件配置

目录 Install Question Q1&#xff1a;字体乱码 Q2&#xff1a;插件配置 Q3&#xff1a;安装扩展插件 Q4&#xff1a;查看安装插件状态 Q5&#xff1a;查看默认插件 Q6&#xff1a;卸载插件 Q7&#xff1a;增加用户配置 Install Github地址&#xff1a;GitHub - chxu…