整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

vue 封装图片预览组件 图片放大、缩小、旋转

装步骤

插入body,制作全局遮罩层、禁止背景滚动

创建一个组件插入body中,使用固定定位生成一个全局遮罩层,再把图片放进去

创建组件/components/PreviewImage/index.vue

搭建组件结构,传入url数组,以及打开的当前图片索引下标,以便进行翻页查看

如果遮罩层后面页面有滚动条时在组件打开时,需要禁止背景内容随鼠标滚轮滚动,每次打开关闭时给body动态增加样式 overflow: hidden属性即可

<template>
  <div v-if="show" class="previewImage_wrapper">
      <div class="previewImage_image">
        <img :src="previewImgList[currentIndex] || ''">
      </div>
      <div class="previewImage_close previewImage_btn" @click="closePreviewImage">×</div>
      <div class="previewImage_navigation">
        <span class="previewImage_navigation_left previewImage_btn" @click="prevImage"><</span>
        <span class="previewImage_navigation_right previewImage_btn" @click="nextImage">></span>
      </div>
    </div>
</template>
<script>
export default {
  props: {
    visible: { // 显示控制
      type: Boolean,
      default: false
    },
    previewImgList: { // url数组
      type: Array,
      default: () => []
    },
    currentIndex: { // 当前图片索引
      type: Number,
      default: 0
    }
  },
  computed: {
    // 双向绑定
    show: {
      get() {
        return this.visible
      },
      set(newVal) {
        this.$emit('update:visible', newVal)
      }
    }
  },
  watch: {
    visible: { // 给body动态增加style属性,禁止背景内容的鼠标滚轮滚动
      handler(newVal) {
        if(newVal) {
          document.body.style.overflow = "hidden";
        } else {
          document.body.style.overflow = "";
        }
      }
    },
  },
  methods: {
    // 上一张图片
    prevImage() {
      if (this.currentIndex === 0) {
        this.currentIndex = this.previewImgList.length - 1
      } else {
        this.currentIndex--
      }
    },
    // 下一张图片
    nextImage() {
      if (this.currentIndex === this.previewImgList.length - 1) {
        this.currentIndex = 0
      } else {
        this.currentIndex++
      }
    },
    // 关闭预览图片组件
    closePreviewImage() {
      this.show = false
    }
  },
  mounted() { // 插入body
    document.body.appendChild(this.$el);
  },
  destroyed() { // 组件销毁后同步清除元素
    this.$el.parentNode.removeChild(this.$el);
  }
}

定义组件过渡动画

在打开遮罩层时加上过渡效果,让组件体验更好

定义过渡动画,使用vue推荐使用自带的 组件,这样切换显示隐藏都会触发过渡效果,如果以class类名的形式定义的过渡动画,在使用指令时v-if或者v-show 隐藏关闭时不会触发结束的过渡效果

<transition name="zoom">
    <!-- 组件 -->
</transition>
.zoom-enter, .zoom-leave-to { // 元素进入和离开时的动作
  transform: scale(0);
}
.zoom-enter-active, .zoom-leave-active { // 元素进入和离开时的过渡动画定义
  transition: transform 0.3s;
}

效果如下

效果实现了,接下来还可以加入更多的功能

结合 transform 实现图片控制

在图片底部加一个控制工具栏,例如对预览图片的控制,放大、缩小、翻转等。在封装组件实现功能的时候我们应当先实现基础功能,再深入开发细节功能

想要实现元素的放大缩小,翻转,可以直接利用css3中transform属性中的 scale rotate,然后使用 js 进行动态控制

<script>
export default {
    data() {
      return {
        imgHandle: { // 图片控制
          scale: 1,
          rotate: 0
        }
      }
    },
    methods: {
    // 初始化还原图片缩放旋转控制
    async initImgHandle() {
      this.imgHandle = {
        scale: 1,
        rotate: 0
      }
      await this.$nextTick()
      const element = this.$refs.previewImage_img
      element.style.transform = `scale(${this.imgHandle.scale}) rotate(${this.imgHandle.rotate}deg)`
    },
    // 放大图片
    async largeHandle() {
      console.log(this.imgHandle.scale, 'scale')
      this.imgHandle.scale = Number((this.imgHandle.scale + 0.2).toFixed(2)) // 使用toFixed防止小数点精度不准
      const element = this.$refs.previewImage_img
      element.style.transform = `scale(${this.imgHandle.scale}) rotate(${this.imgHandle.rotate}deg)`
    },
    // 缩小图片
    async shrinkHandle() {
      console.log(this.imgHandle.scale, 'scale')
      if (this.imgHandle.scale === 0.2) { // 最低缩放到0.2倍
        return
      }
      this.imgHandle.scale = Number((this.imgHandle.scale - 0.2).toFixed(2)) // 使用toFixed防止小数点精度不准
      const element = this.$refs.previewImage_img
      element.style.transform = `scale(${this.imgHandle.scale}) rotate(${this.imgHandle.rotate}deg)`
    },
    }
}
</script>

在进行小数点计算的时候,要注意小数点的精度问题,不然可能会导致计算出错产生bug,这里使用 toFixed 来解决下

在 JavaScript 中处理小数计算时,会遇到舍入误差导致计算结果不准确的情况。这是由于 JavaScript 中采用的是双精度浮点数格式(IEEE 754 标准)来表示数字,而这种格式无法准确地表示某些十进制小数

接下来写旋转的方法,然后给元素绑定点击事件就ok了

<script>
export default {
    methods: {
        // 向左翻转
    async turnLeftHandle() {
      this.imgHandle.rotate = this.imgHandle.rotate - 90
      await this.$nextTick()
      const element = this.$refs.previewImage_img
      element.style.transform = `scale(${this.imgHandle.scale}) rotate(${this.imgHandle.rotate}deg)`
    },
    // 向右翻转
    async turnRightHandle() {
      this.imgHandle.rotate = this.imgHandle.rotate + 90
      await this.$nextTick()
      const element = this.$refs.previewImage_img
      element.style.transform = `scale(${this.imgHandle.scale}) rotate(${this.imgHandle.rotate}deg)`
    },
    }
}
</script>

最后记得给img图片加上过渡效果 transition: transform 0.3s ease; ,当控制图片操作的时候更平滑

最后的效果如下

组件代码

组件完整代码/components/PreviewImage/index.vue

<template>
  <transition name="zoom">
    <div v-if="show" class="previewImage_wrapper" @wheel="handleScroll">
      <div class="previewImage_image">
        <img ref="previewImage_img" :src="previewImgList[currentIndex] || ''">
      </div>
      <div class="previewImage_close previewImage_btn" @click="closePreviewImage">×</div>
      <div class="previewImage_navigation">
        <span class="previewImage_navigation_left previewImage_btn" @click="prevImage"><</span>
        <span class="previewImage_navigation_right previewImage_btn" @click="nextImage">></span>
      </div>
      <div class="previewImage_toolbar">
        <span class="previewImage_btn" @click="shrinkHandle">-</span>
        <span class="previewImage_btn" @click="largeHandle">+</span>
        <span class="previewImage_btn" @click="turnLeftHandle">↺</span>
        <span class="previewImage_btn" @click="initImgHandle">▣</span>
        <span class="previewImage_btn" @click="turnRightHandle">↻</span>
      </div>
    </div>
  </transition>
</template>
<script>
export default {
  props: {
    visible: { // 显示控制
      type: Boolean,
      default: false
    },
    previewImgList: { // url数组
      type: Array,
      default: () => []
    },
    currentIndex: { // 当前图片索引
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      imgHandle: { // 图片控制
        scale: 1,
        rotate: 0
      }
    }
  },
  computed: {
    // 双向绑定
    show: {
      get() {
        return this.visible
      },
      set(newVal) {
        this.$emit('update:visible', newVal)
      }
    }
  },
  watch: {
    visible: { // 给body动态增加style属性,禁止背景内容的鼠标滚轮滚动
      handler(newVal) {
        if(newVal) {
          document.body.style.overflow = "hidden";
          this.initImgHandle() // 每次打开图片初始化
        } else {
          document.body.style.overflow = "";
        }
      }
    },
  },
  methods: {
    // 鼠标滚轮
    handleScroll(event) {
      if (event.deltaY > 0) {
        // 向下滚动事件
        // console.log('向下滚动');
        this.shrinkHandle()
      } else {
        // 向上滚动事件
        // console.log('向上滚动');
        this.largeHandle()
      }
    },
    // 向左翻转
    async turnLeftHandle() {
      this.imgHandle.rotate = this.imgHandle.rotate - 90
      await this.$nextTick()
      const element = this.$refs.previewImage_img
      element.style.transform = `scale(${this.imgHandle.scale}) rotate(${this.imgHandle.rotate}deg)`
    },
    // 向右翻转
    async turnRightHandle() {
      this.imgHandle.rotate = this.imgHandle.rotate + 90
      await this.$nextTick()
      const element = this.$refs.previewImage_img
      element.style.transform = `scale(${this.imgHandle.scale}) rotate(${this.imgHandle.rotate}deg)`
    },
    // 初始化还原图片缩放旋转控制
    async initImgHandle() {
      this.imgHandle = {
        scale: 1,
        rotate: 0
      }
      await this.$nextTick()
      const element = this.$refs.previewImage_img
      element.style.transform = `scale(${this.imgHandle.scale}) rotate(${this.imgHandle.rotate}deg)`
    },
    // 放大图片
    async largeHandle() {
      console.log(this.imgHandle.scale, 'scale')
      this.imgHandle.scale = Number((this.imgHandle.scale + 0.2).toFixed(2)) // 使用toFixed防止小数点精度不准
      const element = this.$refs.previewImage_img
      element.style.transform = `scale(${this.imgHandle.scale}) rotate(${this.imgHandle.rotate}deg)`
    },
    // 缩小图片
    async shrinkHandle() {
      console.log(this.imgHandle.scale, 'scale')
      if (this.imgHandle.scale === 0.2) { // 最低缩放到0.2倍
        return
      }
      this.imgHandle.scale = Number((this.imgHandle.scale - 0.2).toFixed(2)) // 使用toFixed防止小数点精度不准
      const element = this.$refs.previewImage_img
      element.style.transform = `scale(${this.imgHandle.scale}) rotate(${this.imgHandle.rotate}deg)`
    },
    // 上一张图片
    prevImage() {
      if (this.currentIndex === 0) {
        this.currentIndex = this.previewImgList.length - 1
      } else {
        this.currentIndex--
      }
      this.initImgHandle()
    },
    // 下一张图片
    nextImage() {
      if (this.currentIndex === this.previewImgList.length - 1) {
        this.currentIndex = 0
      } else {
        this.currentIndex++
      }
      this.initImgHandle()
    },
    // 关闭预览图片组件
    closePreviewImage() {
      this.show = false
    }
  },
  mounted() { // 插入body
    document.body.appendChild(this.$el);
  },
  destroyed() { // 组件销毁后同步清除元素
    this.$el.parentNode.removeChild(this.$el);
  }
}
</script>
<style lang="less" scoped>
.previewImage_wrapper{
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: rgba(0, 0, 0, .5);
  z-index: 9999;
  .previewImage_image{
    display: flex;
    align-items: center;
    justify-content: center;
    img {
      width: 100vw;
      height: 100vh;
      object-fit: scale-down;
      transition: transform 0.3s ease; 
    }
  }
  .previewImage_close{
    position: absolute;
    right: 20px;
    top: 20px;
    transition: transform 0.2s ease-out;
    &:hover{
      transform: scale(1.2);
    }
  }
  .previewImage_navigation{
    &_left{
      position: absolute;
      left: 15px;
      top: 50%;
      transform: translate(0, -50%);
      transition: transform 0.2s ease-out;
    }
    &_right{
      position: absolute;
      right: 15px;
      top: 50%;
      transform: translate(0, -50%);
      transition: transform 0.2s ease-out;
    }
    &_left:hover,&_right:hover{
      transform: translate(0, -50%) scale(1.2);
    }
  }
  .previewImage_toolbar{
    position: absolute;
    bottom: 10px;
    left: 50%;
    transform: translate(-50%, 0);
    display: flex;
    align-items: center;
    span{
      margin-right: 10px;
      transition: transform 0.2s ease-out;
      &:hover{
        transform: scale(1.1) ;
      }
    }
    span:last-child{
      margin-right: 0;
    }
  }
  .previewImage_btn{
    width: 50px;
    height: 50px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24px;
    color: #fff;
    background-color: #606266;
    border-radius: 50%;
    cursor: pointer;
  }
}
.zoom-enter, .zoom-leave-to { // 元素进入和离开时的动作
  transform: scale(0);
}
.zoom-enter-active, .zoom-leave-active { // 元素进入和离开时的过渡动画定义
  transition: transform 0.3s;
}

.slide-enter, .slide-leave-to { // 元素进入和离开时的动作
  transform: translateX(100%);
}
.slide-enter-active, .slide-leave-active { // 元素进入和离开时的过渡动画定义
  transition: transform 0.3s ease-in-out;
}
</style>  

使用组件代码

这样一个需求,就是在一个DIV中包含有一个Image标签,但是在Div标签中包含有一张背景图片,设计图上的样子是这张背景图片是有一个透明度的,但是如果直接使用opacity属性设置的的话就会连Div中的内容的透明度也会受到影响,那么我们如何在HTML中设置div背景图片的透明度呢?,可以通过以下几种方法实现。

方法一:使用伪元素

这是在日常开发中被推荐使用的方法,通过这种方式实现不会影响到div中的其他内容的透明度只会影响它自己背景的透明度,详细实现如下。

<!DOCTYPE html>
<html>
<head>
    <style>
        .container {
            position: relative;
            width: 300px;
            height: 200px;
            overflow: hidden;
        }

        .container::before {
            content: "";
            background-image: url('your-image.jpg');
            background-size: cover;
            background-position: center;
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            opacity: 0.5; /* 调整透明度 */
            z-index: 1;
        }

        .content {
            position: relative;
            z-index: 2;
            color: white;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="content">
            这里是内容
        </div>
    </div>
</body>
</html>

方法二:使用RGBA颜色叠加

这种方式比较适合那种需要给背景图片上添加蒙版的情况,但是笔者尝试的时候,结果实在是不尽人意。所以还是选择了上面的推荐方法,不过这种方式要比上面的那种方式实现起来要简单很多。如下所示。

<!DOCTYPE html>
<html>
<head>
    <style>
        .container {
            width: 300px;
            height: 200px;
            background: rgba(255, 255, 255, 0.5) url('your-image.jpg') no-repeat center center;
            background-size: cover;
        }
    </style>
</head>
<body>
    <div class="container">
        这里是内容
    </div>
</body>
</html>

方法三:使用CSS滤镜

这种方式实现会影响到整个的div的样式,也就是说页面中的内容的透明度也会受到影响,并且这种影响不会被其他样式所改变。如下所示。

<!DOCTYPE html>
<html>
<head>
    <style>
        .container {
            width: 300px;
            height: 200px;
            background: url('your-image.jpg') no-repeat center center;
            background-size: cover;
            filter: opacity(0.5); /* 调整透明度 */
        }
    </style>
</head>
<body>
    <div class="container">
        这里是内容
    </div>
</body>
</html>

以上就是实现如何调整div的背景透明度,在一些特殊场景中我们还可以通过JS的方式来实现。上面的方法中,推荐使用的是伪元素方法,因为它在修改了div背景透明度之后,并不会影响到其他的元素,RGBA色彩添加则是局限于一些色彩华丽的地方使用,而对于一些单色调的内容来讲这种方式实现效果不是太好。通过CSS过滤样式,虽然是最直接的方式,但是如果在div内部有内容的情况下会影响到整个组件体系的样式。

在实际开发中,我们可以选择合适的方式来实现这个需求。当然还有其他的实现方式,有兴趣的读者可以留言我们一起讨论。

S方法:
$("body").attr('style','overflow-y:hidden') //这个是解决竖状滚动条
//横向 需要把
overflow-y改成overflow-x即可
CSS办法:

一、防止图片撑破DIV方法一

原始处理方法是将要展示的图片进行处理。比如你的DIV宽度为500px像素,那你上传的图片或放入网页的图片宽度就要小于500px,也就是你图片需要图片软件剪切、等比例缩小方法处理后再上传、放入网页中解决撑破撑开DIV问题。

常见很多大型图片站点、新闻站点都是将照片图片进行处理适应网页宽度情况下,进行图片编辑处理的。

二、防止图片撑开DIV方法二

如果不处理照片方法适应DIV有限宽度,那可以对DIV设置隐藏超出内容方法。只需要对DIV设置宽度后加入CSS样式“overflow:hidden”即可解决隐藏图片比DIV过宽部分解决撑破DIV问题

三、解决方法三

对图片img标签中只加入宽度即可解决。这样可以等比例缩小图片,不会影响图片画面质量。

比如你的网页DIV宽度为500px,那你上传图片后对img标签设置width等于500以下即可。
<img src="图片路径" width="小于你的DIV宽度" />即可解决图片过宽导致DIV SPAN撑破,这样好处可以等比例放大缩小图片

四、CSS解决撑破方法四

这种方法使用CSS直接对div内的img进行宽度设置,这样不好是如果图片过小会影响网页浏览图片时候效果。

Div结构:<div class="dc5"><img src="图片路径" /></div>
对应CSS代码:.dc5 img{宽度值+单位}

五、CSS解决图片撑破撑开DIV方法五

使用max-width(最大宽度),比如你DIV宽度为500px,那你对应DIV样式再加入最大宽度CSS样式“max-width="500px"”即可解决,但是在IE6浏览器不兼容此属性,谨慎使用。

六、解决图片撑破DIV层方法总结与推荐

1)、最大宽度(max-width)+overflow:hidden。我们这样设置可以让IE6版本以上浏览器支持最大宽度样式,也让IE6下隐藏图片超出宽度而撑开DIV得到解决,此方法比较方便和实用。

2)、只使用overflow:hidden属性,如方法二

3)、图片使用上传时候软件处理下,以适应DIV布局宽度,如方法一

以上为推荐解决IMG图片撑破有限DIV宽度方法,根据实际情况大家可以任意选择适合自己解决网页中图片撑破DIV层方法。