ionic中滑动缩放的焦点图特效实现

来源:互联网 发布:如何制作淘宝图片 编辑:程序博客网 时间:2024/06/12 01:14

在之前项目整理中,实现过这样一个特效:当用手指滑动时,焦点图随着滑动的距离而成比例的缩放的效果,常见于一些App上,主要是用于展示信息的卡片,相关技术栈版本,ionic1, angular1, 这里再来说一下:技术栈不同,但实现的思路是想通的,仅供开发参考。

效果预览

这里写图片描述

这里由于gif生成工具生成的图片很大,没有展示全部的功能,demo上可以切换不同类型的杂志以及一些切换的特效,并且如果是在线的图片,代码内还进行了懒加载处理。

github演示地址

https://github.com/johnnynode/ionic-samples/blob/ionic-v1/mds/scaleBanners.md

View层布局

<ion-view class="carousel" cache-view="false">  <ion-header-bar class="bar-stable">    <button ng-click="back()">      <i class="ion-arrow-left-c"></i> Back    </button>    <h1 class="title">缩放Banner</h1>  </ion-header-bar>  <ion-content scroll="false" class="no-scroll-bar" overflow-scroll="true" data-tap-disable="true" scrollbar-y="false"               has-bouncing="false">    <!-- 轮播部分 -->    <div class="magazine-slider" ng-class="{'cur':overRate}">      <div carousel-slider data-vol-list="volList"></div>    </div>    <!-- loading部分 -->    <ion-spinner class="center-center loading-bubbles" icon="bubbles" ng-if="isLoading"></ion-spinner>  </ion-content>  <!-- 杂志层 -->  <div class="magazine-wrap">    <div class="magazine-cate" ng-class="{cur:spread}" on-tap="spread=!spread">      <span class="magazine-cate-title">杂志·{{curMag}}</span>      <!-- 两个三角 -->      <span class="magazine-arr-wrap" ng-class="{cur:spread}">                <span class="ion-ios-arrow-down"></span>                <span class="ion-ios-arrow-up"></span>            </span>    </div>    <!-- 下拉菜单 -->    <div class="magazine-cate-cont" ng-class="{cur:spread,'bd-t':spread}">      <ion-scroll has-bouncing="true" scrollbar-y="true" direction="y" style="width: 100%; height: 100%"                  overflow-scroll="false" class="scroll-content">        <ul>          <li ng-repeat="item in magList track by $index" on-tap="fn.switchVols($index,item)">            {{item.magName}}          </li>        </ul>      </ion-scroll>    </div>  </div>  <!-- 灰层 -->  <div class="gray-layer" ng-if="spread" ng-click="fn.hideGray()"></div></ion-view>

Controller逻辑

.controller('MarouselCtrl', [    '$scope',    '$timeout',    'appUtils',    'CarouselData',    function ($scope, $timeout, appUtils, CarouselData) {      /* 初始化数据模型 */      $scope.back = appUtils.back;      $scope.magList = []; // 杂志列表      $scope.volList = []; // 期列表数据      $scope.curMag = '最新'; // 默认杂志 : 最新      $scope.isLoading = true; // loading 默认 true      var fn = $scope.fn = {};      /* 进入视图,收起 */      $scope.$on('$ionicView.beforeEnter', function () {        $scope.spread = false; // 默认不展开      });      pageInit();      /* 页面初始化 */      function pageInit() {        loadingHide(); // loading 效果        getMagList(); // 获取杂志列表        getLatest(); // 获取最新杂志      }      /* 图片质量较大 添加延迟隐藏方法 */      function loadingHide() {        var t = $timeout(function () {          $scope.isLoading = false;          $timeout.cancel(t); // 去除延定时器        }, 300);      }      /* 获取杂志列表 */      function getMagList() {        var json = {          "magName": "最新"        };        $scope.magList.push(json);        $scope.magList = $scope.magList.concat(CarouselData.magList);      }      /* 获取最新期 */      function getLatest() {        $scope.volList = CarouselData.latest; // 最新期数据      }      /* 根据杂志code获取该杂志的期 */      function getVolByMagCode(magCode) {        switch (magCode) {          case "ECON":            $scope.volList = CarouselData.ecoList;            break;          case "BIOL":            $scope.volList = CarouselData.bioList;            break;          case "COMP":            $scope.volList = CarouselData.comList;            break;          default:            console.log("not match");            $scope.volList = CarouselData.latest; // 分配给最新期        }      }      /* 隐藏灰层 */      fn.hideGray = function() {        $scope.spread = false;      }      /* 杂志的点击 */      fn.switchVols = function (index, item) {        $scope.spread = false; // 默认收起        $scope.isLoading = $scope.curMag !== item.magName; // 表示切换了        // 没有loading ,不去请求数据        if (!$scope.isLoading) return;        // 有loading, 设置效果        loadingHide();        // 点击第一个获取最新数据        if (!index) {          $scope.curMag = '最新';          return getLatest();        }        getVolByMagCode(item.magCode);        $scope.curMag = item.magName;      }    }  ]);

Directive指令

.directive('carouselSlider', function (appUtils, $compile, $timeout) {      return {        restrict: 'A',        scope: {          volList: '=',          charShow: '='        },        template: '<div class="slider-wrap"></div>',        link: function (scope) {          // 用于挂载在外部的变量, 用于处理屏幕变化的变量          scope.outWatcher = {};          // 所有设置函数          function setUp() {            // 针对宽高比的判断            // 进行轮播图的 dom 生成操作            var $ = angular.element; // jqLite 对象            var slideBox = scope.outWatcher.slideBox = document.querySelector('.slider-wrap'); // 获取轮播盒子对象            var sliderInner = scope.outWatcher.sliderInner = document.createElement('ul');            sliderInner.className = 'slider-wrap-inner';            slideBox.appendChild(sliderInner);            // 杂志点击的回调 可用于其他逻辑处理            scope.magClick = function (title) {              console.log(title);            };            // 缩放的动画            function scale(obj, rate) {              if (!obj) return;              obj.style.transform = "scale(" + rate + ")";              obj.style.webkitTransform = "scale(" + rate + ")";            }            // 获取数据            function getData(list, callback) {              // 通过获得的数据,生成节点操作              for (var i = 0; i < list.length; i++) {                var li = document.createElement('li');                var img = document.createElement('img');                var imgWrap = document.createElement('div');                imgWrap.className = 'img-wrap';                img.src = 'images/transparent.gif';                // 首先先加载前三张图片的地址,其他的作懒加载处理                if (i < 3) {                  img.setAttribute('style', 'background-image:url(' + list[i].coverimg + ')');                }                imgWrap.appendChild(img);                li.appendChild(imgWrap);                li.setAttribute('on-tap', 'magClick("' + list[i].title + '")'); // 绑定事件                $(sliderInner).append($(li));              }              var htmlObj = $compile($(sliderInner).html())(scope); // 对html 进行重新编译              $(sliderInner).html(''); // 清空              $(sliderInner).append(htmlObj); // 追加              var lis = sliderInner.querySelectorAll('li'); // 得到当前的所有li对象              var imgs = scope.outWatcher.imgs = []; // 用于存放图像包裹节点              // 图像包裹节点数组, 初始化样式              for (var k = 0; k < lis.length; k++) {                if (!k) {                  imgs.push(lis[0].querySelector('.img-wrap')); // 第一个只 push 进去 ,不 设置样式                  continue;                }                var item = lis[k].querySelector('.img-wrap');                scale(item, 252 / 291); // 样式初始化缩放                imgs.push(item); // 并push              }              callback && angular.isFunction(callback) && callback(imgs, list); // 将数据通过callback带走            }            // 针对杂志切换,数据同时切换            scope.$watch('volList', function (now) {              if (now && now.length) {                sliderInner.innerHTML = ''; // 先清空内容                // 使用$timeout来解决宽度问题,重新渲染dom.                $timeout(function(){                  getData(now, function (imgs, now) {                    var m = new MobileMove(); // 重新new                    m.setSwipe(slideBox, sliderInner, imgs, now);                  });                });              }            });          }          // 监听屏幕变化事件, 随时构造对象          window.onresize = function() {            var m = new MobileMove(); // 重新new            m.setSwipe(scope.outWatcher.slideBox, scope.outWatcher.sliderInner, scope.outWatcher.imgs, scope.volList);          }          // 页面加载完成后执行          var contentLoaded = scope.$watch('$viewContentLoaded', function() {            setUp(); // 全面设置            contentLoaded(); // 取消 watch          });        }      };    })

模拟的假数据

.factory('CarouselData', [      function () {        return {          // 杂志列表          magList: [            {              "magName": "经济学",              "magCode": "ECON"            },            {              "magName": "生物技术",              "magCode": "BIOL"            },            {              "magName": "计算机科技",              "magCode": "COMP"            }          ],          // 最新杂志          latest: [            {              "coverimg": "images/carousel/latest01.jpg",              "title": "民间文学",              "magCode": "LITE",            },            {              "coverimg": "images/carousel/latest02.jpg",              "title": "视觉传播",              "magCode": "COAR"            },            {              "coverimg": "images/carousel/latest03.jpg",              "title": "光量子器件及通信",              "magCode": "COMM"            },            {              "coverimg": "images/carousel/latest04.jpg",              "title": "营养管理",              "magCode": "FOOD"            },            {              "coverimg": "images/carousel/latest05.jpg",              "title": "晶体材料",              "magCode": "MATE"            }          ],          // 经济学杂志          ecoList:[            {              "coverimg": "images/carousel/econ01.jpg",              "title": "经济增长",              "magCode": "ECON"            },            {              "coverimg": "images/carousel/econ02.jpg",              "title": "绿色经济",              "magCode": "ECON"            },            {              "coverimg": "images/carousel/econ03.jpg",              "title": "网络经济",              "magCode": "ECON"            },            {              "coverimg": "images/carousel/econ04.jpg",              "title": "蓝海战略",              "magCode": "ECON"            },            {              "coverimg": "images/carousel/econ05.jpg",              "title": "电子商务市场",              "magCode": "ECON"            }          ],          // 生物技术          bioList:[            {              "coverimg": "images/carousel/biol01.jpg",              "title": "农业生物技术",              "magCode": "BIOL"            },            {              "coverimg": "images/carousel/biol02.jpg",              "title": "生物能源技术",              "magCode": "BIOL"            },            {              "coverimg": "images/carousel/biol03.jpg",              "title": "特色农业生物技术",              "magCode": "BIOL"            },            {              "coverimg": "images/carousel/biol04.jpg",              "title": "基因组育种",              "magCode": "BIOL"            },            {              "coverimg": "images/carousel/biol05.jpg",              "title": "食品生物技术",              "magCode": "BIOL"            }          ],          // 计算机          comList:[            {              "coverimg": "images/carousel/comp01.jpg",              "title": "计算语言学",              "magCode": "COMP"            },            {              "coverimg": "images/carousel/comp02.jpg",              "title": "机器学习",              "magCode": "COMP"            },            {              "coverimg": "images/carousel/comp03.jpg",              "title": "云计算",              "magCode": "COMP"            },            {              "coverimg": "images/carousel/comp04.jpg",              "title": "互联网创新应用",              "magCode": "COMP"            },            {              "coverimg": "images/carousel/comp05.jpg",              "title": "移动计算",              "magCode": "COMP"            }          ]        }      }    ]);

用于特效的底层脚本封装

(function (window) {  var MobileMove = function () {  };  MobileMove.prototype = {    addTransition: function (obj, time) {      obj.style.transition = "all " + time + "s ease";      obj.style.webkitTransition = "all " + time + "s ease";    },    removeTransition: function (obj) {      obj.style.transition = "none";      obj.style.webkitTransition = "none";    },    changeTranslateX: function (obj, x) {      obj.style.transform = "translateX(" + x + "px)";      obj.style.webkitTransform = "translateX(" + x + "px)";    },    transitionEnd: function (obj, callback) {      /*当是对象的时候绑定事件*/      if (typeof obj === 'object') {        obj.addEventListener('transitionEnd', function (e) {          callback && callback(e);        }, false);        obj.addEventListener('webkitTransitionEnd', function (e) {          callback && callback(e);        }, false);      }    },    /* 模仿的tap事件 */    tap: function (obj, callback) {      /* 点击事件 超过200ms */      if (typeof  obj !== 'object') return false;      var startTime = 0,        isMove = false; // 来标记我们是否移动过      obj.addEventListener('touchstart', function () {        startTime = Date.now(); // 取当前时间      }, false);      obj.addEventListener('touchmove', function () {        isMove = true;      }, false);      window.addEventListener('touchend', function (e) {        /* 响应时间小于200ms 并且没有滑动过 */        if (Date.now() - startTime < 200 && !isMove) {          callback && callback.apply(obj, e);        }        startTime = 0;        isMove = false;      }, false);    },    /* 缩放动画 */    scale: function (obj, rate) {      if (!obj) return;      obj.style.transform = "scale(" + rate + ")";      obj.style.webkitTransform = "scale(" + rate + ")";    },    /* 缩放动画的还原 */    scaleBack: function (obj,index) {      var that = this;      if (!obj) return;      for(var i=0;i<obj.length;i++){        if(i === index){          that.scale(obj[index],1); // 当前缩放为1          continue;        }        that.scale(obj[i],252/291); // 其他缩放回归默认值      }    },    setSwipe: function (obj, obj_move, imgs, list) {      if (typeof  obj !== 'object') return false;      var that = this;      var num = imgs.length; // 获取当前节点数      var startX = 0; // 开始你的X的位置      var endX = 0; // 停止滑动的时候的X的位置      var distanceX = 0; // 是改变的距离      var _distanceX = 0; // 算比率时用到      var index = 0; // 滑动到第几张图片      var super_width = obj.clientWidth; // 最大的盒子 ,相当于最外面的宽度 或者和 window.innerWidth 相同.      var width = super_width * (291 / 375); // 图片每次移动的宽度 , 临界距离 291 是 img-wrap 所占宽度 (根据设计图来的比例)      that.removeTransition(obj_move); // 初始去除过度      that.changeTranslateX(obj_move, 0); // 初始化X距离      // 针对事件的监听      obj.addEventListener('touchstart', function (e) {        e.preventDefault();        startX = e.touches[0].clientX;        if(index < imgs.length -2){          imgs[index+2].querySelector('img').setAttribute('style', 'background-image:url(' + list[index+2].coverimg + ')');        }      }, false);      obj.addEventListener('touchmove', function (e) {        e.preventDefault();        endX = e.touches[0].clientX;        distanceX = startX - endX; // 获取移动距离        // distanceX > 0 滑动方向 true => 左滑 无需考虑 0        _distanceX = distanceX > 0 && distanceX > width ? width : distanceX; // 移动距离>宽度时 ? 移动距离=宽度        _distanceX = !(distanceX > 0) && distanceX < -width ? -width : distanceX; // 移动距离<-宽度时 ? 移动距离=-宽度        var rate = Math.abs(_distanceX / width); // 缩放比率        if (!(distanceX > 0) && !index || distanceX > 0 && index === num - 1) {          // DO NOTHING 此处做过滤        } else {          if (distanceX > 0) {            // 左滑时缩放            that.scale(imgs[index], 1 - (1 - 252 / 291) * rate); // 当前的缩小  252/291 或者 344/382 这个是缩放比            // 下一个放大            that.scale(imgs[index + 1], (252 / 291 + (1 - 252 / 291) * rate) <1 ? (252 / 291 + (1 - 252 / 291) * rate) : 1);           } else {            // 右滑时缩放            that.scale(imgs[index], 1 - (1 - 252 / 291) * rate); // 当前的缩小            that.scale(imgs[index - 1], 252 / 291 + (1 - 252 / 291) * rate); // 上一个放大          }        }        that.removeTransition(obj_move); // 去除过渡        that.changeTranslateX(obj_move, -index * width - distanceX); // 同步盒子移动      }, false);      obj.addEventListener('touchend', function (e) {        e.preventDefault();        // 移动结束还原缩放        if (!(distanceX > 0) && !index || distanceX > 0 && index === num - 1) {          that.scale(imgs[index], 1);        }        /* 满足1/3的时候滑动一次 */        if (Math.abs(distanceX) > 1 / 3 * width && endX) {          // 进行 index 加工过滤          index = distanceX > 0 ? ++index : --index; // 根据方向判断中间值          index = index <= 0 ? 0 : index;  // 判断第一个时          index = index >= num - 1 ? num - 1 : index;  // 判断最后一个时          that.addTransition(obj_move, 0.2); // 加上过渡效果          that.changeTranslateX(obj_move, -index * width); // 滑动        } else {          // 当不满足1/3的时候吸附回去          that.addTransition(obj_move, 0.2); // 加上过渡效果          that.changeTranslateX(obj_move, -index * width);        }        that.scaleBack(imgs,index); // 恢复原始缩放比        // 每次滑动结束 , 恢复初始值        startX = 0;        endX = 0;        distanceX = 0;      }, false);    }  };  // 暴露对象  window.MobileMove = MobileMove;})(window);

总结

合理的特效依赖合理的布局设定以及合理的数据结构,上述demo用到了很多移动端事件和相关的动画处理,这里不一一赘述,重要的是提供一种解决问题的思路。

原创粉丝点击