关于前端:从张鑫旭的demo中我学到了图像拉伸的原理

32次阅读

共计 6463 个字符,预计需要花费 17 分钟才能阅读完成。

文章收录:

  • 集体网址: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 录屏所示:

能够点击这里体验: 图像拉伸宽高扭转。

图像是有比例的,如常见比例 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 源码登程,本人也对图像拉伸的性能实现了一遍,做到脑会手也会。当前再有相似的性能分分钟搞定它!

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

如果我的文章对你有帮忙,你的👍就是对我的最大反对 ^_^。

正文完
 0