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用到了很多移动端事件和相关的动画处理,这里不一一赘述,重要的是提供一种解决问题的思路。
阅读全文
0 0
- ionic中滑动缩放的焦点图特效实现
- ionic:实现滑动的三种方式
- ionic实现可滑动的tab
- JS中简单的滑动特效
- 滑动菜单特效实现
- ionic实现长图显示与双指缩放
- ionic:css实现带字的滑动 toggle
- iOS中UIScrollView嵌套UIImageView实现图片滑动浏览、缩放
- js实现搜索框提示字的特效------------获取焦点和失去焦点
- 焦点图的实现
- 可缩放、滑动显示的折线图
- 滑动焦点图 源码
- Android 如何实现 焦点图的 无线循环滑动的状态?
- ionic中的分类侧边栏ABC字母滑动特效
- JavaScript实现滑动门特效
- Android滑动菜单特效实现
- Android滑动菜单特效实现
- Android滑动菜单特效实现
- HDU6201(spfa)
- 在matlab和opencv中分别实现稀疏表示
- ucos-iii学习之锁住调度器
- VCC、VDD、VDDA、VSS、VSSA
- 文件的读写3
- ionic中滑动缩放的焦点图特效实现
- 求1+....+n的值
- 数据分析师面试
- JZOJ 5354. 【NOIP2017提高A组模拟9.9】导弹拦截
- AJAX实现异步刷新
- ScrollView
- Retrofit2源码的阅读
- Java实现快速排序算法
- 51 nod 最小1的数量 数位DP