百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

微信小程序实现多图片旋转、放大、缩小

bigegpt 2024-08-03 11:35 5 浏览

微信小程序实现多图片旋转、放大、缩小。

想做一个大头贴的功能,本来使用的腾讯AI接口已经实现了,最近接口不能调用,重新找技术,参考网上的提供的代码(https://github.com/peng20017/wx-drop),修改了一些效果和代码,最终实现了多图片上传、旋转等功能。配合我的上一篇文章,可以实现更多的效果,喜欢的可以收藏关注一下,效果图如下:


代码如下:

let index = 0,items = [],flag = true,itemId = 1;
const hCw = 1.62; // 图片宽高比
const canvasPre = 1; // 展示的canvas占mask的百分比
const maskCanvas = wx.createCanvasContext('maskCanvas');
Page({
  /**
   * 页面的初始数据
   */
  data: {
    itemList: [],
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {
    items = this.data.itemList;
    this.drawTime = 0
    // this.setDropItem({
    //   url: '/images/1.png'
    // });
    // this.setDropItem({
    //   url: '/images/1.png'
    // });
    wx.getSystemInfo({
      success: sysData => {
        this.sysData = sysData
        this.setData({
          canvasWidth: this.sysData.windowWidth * canvasPre, // 如果觉得不清晰的话,可以把所有组件、宽高放大一倍
          canvasHeight: this.sysData.windowWidth * canvasPre * hCw,
        })
      }
    })
  },
  addImage(e){
    let that = this;
    wx.chooseImage({
      count: 3, //选择照片的数量
      sizeType: ['original'],
      sourceType: ['album', 'camera'],
      success(res) {
        const tempFilePaths = res.tempFilePaths
        let posHeight = 200,posLeft = 200;
        res.tempFilePaths.forEach(function(item, index){
          let imgData = {
            url: item,
            top:index * posHeight,
            left: ((index+1) % 2 === 0 ? 200 : 0)
          }
          that.setDropItem(imgData);  
        })
      },
      complete(){
        
      },
      fail(){
        wx.hideLoading()
      }
    })
  },
  setDropItem(imgData) {
    let data = {}
    wx.getImageInfo({
      src: imgData.url,
      success: res => {
        // 初始化数据
        data.width = 150; //宽度
        data.height =150; //高度
        data.image = imgData.url; //地址
        data.id = ++itemId; //id
        data.top = imgData.top; //top定位
        data.left = imgData.left; //left定位
        //圆心坐标
        data.x = data.left + data.width / 2;
        data.y = data.top + data.height / 2;
        data.scale = 1; //scale缩放
        data.oScale = 1; //方向缩放
        data.rotate = 1; //旋转角度
        data.active = false; //选中状态
        console.log(data)
        items[items.length] = data;
        this.setData({
          itemList: items
        })
      }
    })
  },
  WraptouchStart: function(e) {
    for (let i = 0; i < items.length; i++) {
      items[i].active = false;
      if (e.currentTarget.dataset.id == items[i].id) {
        index = i;
        items[index].active = true;
      }
    }
    this.setData({
      itemList: items
    })

    items[index].lx = e.touches[0].clientX;
    items[index].ly = e.touches[0].clientY;

    console.log(items[index])
  },
  WraptouchMove(e) {
    if (flag) {
      flag = false;
      setTimeout(() => {
        flag = true;
      }, 100)
    }
    // console.log('WraptouchMove', e)
    items[index]._lx = e.touches[0].clientX;
    items[index]._ly = e.touches[0].clientY;

    items[index].left += items[index]._lx - items[index].lx;
    items[index].top += items[index]._ly - items[index].ly;
    items[index].x += items[index]._lx - items[index].lx;
    items[index].y += items[index]._ly - items[index].ly;

    items[index].lx = e.touches[0].clientX;
    items[index].ly = e.touches[0].clientY;
    console.log(items)
    this.setData({
      itemList: items
    })
  },
  WraptouchEnd() {
    this.synthesis()
  },
  oTouchStart(e) {
    //找到点击的那个图片对象,并记录
    for (let i = 0; i < items.length; i++) {
      items[i].active = false;
      if (e.currentTarget.dataset.id == items[i].id) {
        console.log('e.currentTarget.dataset.id', e.currentTarget.dataset.id)
        index = i;
        items[index].active = true;
      }
    }
    //获取作为移动前角度的坐标
    items[index].tx = e.touches[0].clientX;
    items[index].ty = e.touches[0].clientY;
    //移动前的角度
    items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty)
    //获取图片半径
    items[index].r = this.getDistancs(items[index].x, items[index].y, items[index].left, items[index].top);
    console.log(items[index])
  },
  oTouchMove: function(e) {
    if (flag) {
      flag = false;
      setTimeout(() => {
        flag = true;
      }, 100)
    }
    //记录移动后的位置
    items[index]._tx = e.touches[0].clientX;
    items[index]._ty = e.touches[0].clientY;
    //移动的点到圆心的距离
    items[index].disPtoO = this.getDistancs(items[index].x, items[index].y, items[index]._tx, items[index]._ty - 10)

    items[index].scale = items[index].disPtoO / items[index].r;
    items[index].oScale = 1 / items[index].scale;

    //移动后位置的角度
    items[index].angleNext = this.countDeg(items[index].x, items[index].y, items[index]._tx, items[index]._ty)
    //角度差
    items[index].new_rotate = items[index].angleNext - items[index].anglePre;

    //叠加的角度差
    items[index].rotate += items[index].new_rotate;
    items[index].angle = items[index].rotate; //赋值

    //用过移动后的坐标赋值为移动前坐标
    items[index].tx = e.touches[0].clientX;
    items[index].ty = e.touches[0].clientY;
    items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty)

    //赋值setData渲染
    this.setData({
      itemList: items
    })

  },
  getDistancs(cx, cy, pointer_x, pointer_y) {
    var ox = pointer_x - cx;
    var oy = pointer_y - cy;
    return Math.sqrt(
      ox * ox + oy * oy
    );
  },
  /*
   *参数1和2为图片圆心坐标
   *参数3和4为手点击的坐标
   *返回值为手点击的坐标到圆心的角度
   */
  countDeg: function(cx, cy, pointer_x, pointer_y) {
    var ox = pointer_x - cx;
    var oy = pointer_y - cy;
    var to = Math.abs(ox / oy);
    var angle = Math.atan(to) / (2 * Math.PI) * 360;
    // console.log("ox.oy:", ox, oy)
    if (ox < 0 && oy < 0) //相对在左上角,第四象限,js中坐标系是从左上角开始的,这里的象限是正常坐标系  
    {
      angle = -angle;
    } else if (ox <= 0 && oy >= 0) //左下角,3象限  
    {
      angle = -(180 - angle)
    } else if (ox > 0 && oy < 0) //右上角,1象限  
    {
      angle = angle;
    } else if (ox > 0 && oy > 0) //右下角,2象限  
    {
      angle = 180 - angle;
    }
    return angle;
  },
  deleteItem: function(e) {
    let newList = [];
    for (let i = 0; i < items.length; i++) {
      if (e.currentTarget.dataset.id != items[i].id) {
        newList.push(items[i])
      }
    }
    if (newList.length > 0) {
      newList[newList.length - 1].active = true;
    }
    items = newList;
    this.setData({
      itemList: items
    })
  },
  openMask () {
    if (this.drawTime == 0) {
      this.synthesis()
    }
    this.setData({
      showCanvas: true
    })
  },
  synthesis() { // 合成图片
    this.drawTime = this.drawTime + 1
    console.log('合成图片')
    maskCanvas.save();
    maskCanvas.beginPath();
    //一张白图  可以不画
    maskCanvas.setFillStyle('#fff');
    maskCanvas.fillRect(0, 0, this.sysData.windowWidth, this.data.canvasHeight)
    maskCanvas.closePath();
    maskCanvas.stroke();

    //画背景 hCw 为 1.62 背景图的高宽比
    maskCanvas.drawImage('/images/bg.png', 0, 0, this.data.canvasWidth, this.data.canvasHeight);
    /*
        num为canvas内背景图占canvas的百分比,若全背景num =1
        prop值为canvas内背景的宽度与可移动区域的宽度的比,如一致,则prop =1;
       */
    //画组件
    const num = 1,
      prop = 1;
    items.forEach((currentValue, index) => {
      maskCanvas.save();
      maskCanvas.translate(this.data.canvasWidth * (1 - num) / 2, 0);
      maskCanvas.beginPath();
      maskCanvas.translate(currentValue.x * prop, currentValue.y * prop); //圆心坐标
      maskCanvas.rotate(currentValue.angle * Math.PI / 180);
      maskCanvas.translate(-(currentValue.width * currentValue.scale * prop / 2), -(currentValue.height * currentValue.scale * prop / 2))
      maskCanvas.drawImage(currentValue.image, 0, 0, currentValue.width * currentValue.scale * prop, currentValue.height * currentValue.scale * prop);
      maskCanvas.restore();
    })
    maskCanvas.draw(false, (e) => {
      wx.canvasToTempFilePath({
        canvasId: 'maskCanvas',
        success: res => {
          console.log('draw success')
          console.log(res.tempFilePath)
          this.setData({
            canvasTemImg: res.tempFilePath
          })
        }
      }, this)
    })
  },
  disappearCanvas() {
    this.setData({
      showCanvas: false
    })
  },
  saveImg: function() {
    wx.saveImageToPhotosAlbum({
      filePath: this.data.canvasTemImg,
      success: res => {
        wx.showToast({
          title: '保存成功',
          icon: "success"
        })
      },
      fail: res => {
        console.log(res)
        wx.openSetting({
          success: settingdata => {
            console.log(settingdata)
            if (settingdata.authSetting['scope.writePhotosAlbum']) {
              console.log('获取权限成功,给出再次点击图片保存到相册的提示。')
            } else {
              console.log('获取权限失败,给出不给权限就无法正常使用的提示')
            }
          },
          fail: error => {
            console.log(error)
          }
        })
        wx.showModal({
          title: '提示',
          content: '保存失败,请确保相册权限已打开',
        })
      }
    })
  }
})
/**index.wxss**/
.bg {
  width: 100%;
  height: 100vh;
}
.contentWarp{
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  margin: auto;
  /* background-color: #d1e3f1; */
}
.touchWrap{
    transform-origin: center;
    position: absolute;
    z-index: 100;
}

.imgWrap {
    box-sizing: border-box;
    width: 100%;
    transform-origin: center;
    float: left;
    border: 5rpx transparent dashed;
}
.imgWrap image {
    float: left;
}
.touchActive .x {
    display: block;
}

.touchActive .o {
    display: block;
}

.x {
    position: absolute;
    top: -25rpx;
    left: -25rpx;
    z-index: 500;
    display: none;
    width: 50rpx;
    height: 50rpx;
    overflow: hidden;
    font-weight: bold;
    color: #d1e3f1;
}
.o {
    position: absolute;
    bottom: -25rpx;
    right: -25rpx;
    width: 50rpx;
    height: 50rpx;
    text-align: center;
    display: none;
    overflow: hidden;
    font-weight: bold;
    color: #d1e3f1;
}
.active {
    background-color: rgb(78, 114, 151);
}

.active view {
    border: none;
}
.touchActive {
    /* border: 4rpx #fff dashed; */
    z-index: 400;
}
.fixed {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
}


.canvasWrap {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    background-color: rgba(0, 0, 0, 0.6);
    z-index: 999;
    text-align: center;
}

.maskCanvas {
    position: absolute;
    left: -200%;
    top: 0;
}

.btn {
    font-size: 30rpx;
    color: #81b7c4;
    border: 3rpx solid #81b7c4;
    background-color: #fff;
    line-height: 90rpx;
    width: 50%;
    margin-top: 20rpx;
    height: 90rpx;
}

.btnView view {
    padding-bottom: 20rpx;
}

.hand {
    position: absolute;
    left: 100rpx;
    right: 0;
    margin: auto;
    z-index: 100;
}

.getUserInfoBtn {
    position: initial;
    border: none;
    background-color: none;
}

.getUserInfoBtn::after {
    border: none;
}

.btn_view {
    display: flex;
    padding: 20rpx;
}

.btn_view button {
    width: 300rpx;
    font-size: 30rpx;
    color: #81b7c4;
    border: 3rpx solid #81b7c4;
    background-color: #fff;
    line-height: 90rpx;
}

.resImg {
  width: 75%;
  margin-top: 10px;
}
<view class="container">
  <image class="bg" src="/images/bg.png"></image>
  <view class='contentWarp'>
    <!-- *************操作区域*************  -->
    <block wx:for="{{itemList}}" wx:key="{{item.id}}">

      <view class='touchWrap' style='transform: scale({{item.scale}});top:{{item.top}}px;left:{{item.left}}px; z-index:{{item.active?100:1}}'>
        <view class='imgWrap {{item.active? "touchActive":""}}' style="transform: rotate({{item.angle}}deg); border: {{item.active?4*item.oScale:0}}rpx #fff dashed;">
          <image src='{{item.image}}' data-id='{{item.id}}' style='width:{{item.width}}px;height:{{item.height}}px;' bindtouchstart='WraptouchStart' bindtouchmove='WraptouchMove' bindtouchend='WraptouchEnd'></image>
          <image class='x' src='../../images/x.png' style='transform: scale({{item.oScale}});transform-origin:center;' data-id='{{item.id}}' bindtap='deleteItem'></image>
          <image class='o' src='../../images/o.png' style='transform: scale({{item.oScale}});transform-origin:center;' data-id='{{item.id}}' bindtouchstart='oTouchStart' bindtouchmove='oTouchMove' bindtouchend='WraptouchEnd'></image>
        </view>
      </view>
    </block>
  </view>
  <!-- **************操作区域************  -->
  <view class="fixed" style="margin-bottom:20px">
    <button bindtap="addImage" style="width:45%;heigth:45px;float:left;margin-left:15px">选择照片</button>
    <button class="custom-button" bindtap="openMask" type="primary" style="width:45%;heigth:45px;margin-top:0">预览照片</button>
  </view>

  <view class='canvasWrap' hidden="{{!showCanvas}}">
    <image class="resImg" bindlongtap="saveImg" src="{{canvasTemImg}}" mode="widthFix"></image>
    <view class='btn_view'>
      <button bindtap='saveImg'>保存到手机</button>
      <button bindtap='disappearCanvas'>关闭</button>
    </view>
  </view>

  <!-- bug点:一定不要用if hidden 隐藏canvas会导致很多api调用bug -->
  <canvas class='maskCanvas' canvas-id="maskCanvas" style='width:{{canvasWidth}}px; height:{{canvasHeight}}px;'></canvas>
  
</view>

	

相关推荐

得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践

一、摘要在前端可观测分析场景中,需要实时观测并处理多地、多环境的运行情况,以保障Web应用和移动端的可用性与性能。传统方案往往依赖代理Agent→消息队列→流计算引擎→OLAP存储...

warm-flow新春版:网关直连和流程图重构

本期主要解决了网关直连和流程图重构,可以自此之后可支持各种复杂的网关混合、多网关直连使用。-新增Ruoyi-Vue-Plus优秀开源集成案例更新日志[feat]导入、导出和保存等新增json格式支持...

扣子空间体验报告

在数字化时代,智能工具的应用正不断拓展到我们工作和生活的各个角落。从任务规划到项目执行,再到任务管理,作者深入探讨了这款工具在不同场景下的表现和潜力。通过具体的应用实例,文章展示了扣子空间如何帮助用户...

spider-flow:开源的可视化方式定义爬虫方案

spider-flow简介spider-flow是一个爬虫平台,以可视化推拽方式定义爬取流程,无需代码即可实现一个爬虫服务。spider-flow特性支持css选择器、正则提取支持JSON/XML格式...

solon-flow 你好世界!

solon-flow是一个基础级的流处理引擎(可用于业务规则、决策处理、计算编排、流程审批等......)。提供有“开放式”驱动定制支持,像jdbc有mysql或pgsql等驱动,可...

新一代开源爬虫平台:SpiderFlow

SpiderFlow:新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫。-精选真开源,释放新价值。概览Spider-Flow是一个开源的、面向所有用户的Web端爬虫构建平台,它使用Ja...

通过 SQL 训练机器学习模型的引擎

关注薪资待遇的同学应该知道,机器学习相关的岗位工资普遍偏高啊。同时随着各种通用机器学习框架的出现,机器学习的门槛也在逐渐降低,训练一个简单的机器学习模型变得不那么难。但是不得不承认对于一些数据相关的工...

鼠须管输入法rime for Mac

鼠须管输入法forMac是一款十分新颖的跨平台输入法软件,全名是中州韵输入法引擎,鼠须管输入法mac版不仅仅是一个输入法,而是一个输入法算法框架。Rime的基础架构十分精良,一套算法支持了拼音、...

Go语言 1.20 版本正式发布:新版详细介绍

Go1.20简介最新的Go版本1.20在Go1.19发布六个月后发布。它的大部分更改都在工具链、运行时和库的实现中。一如既往,该版本保持了Go1的兼容性承诺。我们期望几乎所...

iOS 10平台SpriteKit新特性之Tile Maps(上)

简介苹果公司在WWDC2016大会上向人们展示了一大批新的好东西。其中之一就是SpriteKitTileEditor。这款工具易于上手,而且看起来速度特别快。在本教程中,你将了解关于TileE...

程序员简历例句—范例Java、Python、C++模板

个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...

Telerik UI for iOS Q3 2015正式发布

近日,TelerikUIforiOS正式发布了Q32015。新版本新增对XCode7、Swift2.0和iOS9的支持,同时还新增了对数轴、不连续的日期时间轴等;改进TKDataPoin...

ios使用ijkplayer+nginx进行视频直播

上两节,我们讲到使用nginx和ngixn的rtmp模块搭建直播的服务器,接着我们讲解了在Android使用ijkplayer来作为我们的视频直播播放器,整个过程中,需要注意的就是ijlplayer编...

IOS技术分享|iOS快速生成开发文档(一)

前言对于开发人员而言,文档的作用不言而喻。文档不仅可以提高软件开发效率,还能便于以后的软件开发、使用和维护。本文主要讲述Objective-C快速生成开发文档工具appledoc。简介apple...

macOS下配置VS Code C++开发环境

本文介绍在苹果macOS操作系统下,配置VisualStudioCode的C/C++开发环境的过程,本环境使用Clang/LLVM编译器和调试器。一、前置条件本文默认前置条件是,您的开发设备已...