目录
一、实现的效果:
二、实现js的具体步骤
1、需要实现的逻辑梳理
2、切换风格的逻辑
三、完整代码:
用js编写使用animejs实现图片复杂的切换效果
一、实现的效果:
点击——> <——箭头,实现不同动画效果的炫酷切换 。
代码可以通过我下面分享的码云链接进行下载,或者结尾我会附上完整的代码:
animejs_1照片墙: 使用animejs实现照片墙的炫酷切换效果
我们主要来实现js部分的编写,所以建议大家去我的码云把源码下载下来,方便学习。
二、实现js的具体步骤
创建如下的目录:
目录组成部分:
现在的状态是,画面在第一页上,点击两个箭头页面是不动的:
我们可以看到在index.html文件中,这7个风格都在里面,每一个风格我们用类名slide--layout-n中的数字来做区分:
如slide--layout-4代表第4种风格。
这些风格都在页面中显示,为什么我们看不到?是因为透明度改成0。
我们需要做的是给按钮添加点击事件,使之能够切换风格:
1、需要实现的逻辑梳理
点击箭头,会有两个效果:一个显示的图片出去,将要显示的图片进来的效果。
每次切换,标题也是不同的动画效果
总结我们需要完成的业务逻辑就是:
1、 我们切换风格的逻辑是通过改变每个风格的透明度来实现。
2、实现两个动画风格:图片的和标题的。——图片又分为进入和出去两个动画风格;标题每个风格都一样,我们写一个就可以了。
2、切换风格的逻辑
展示页面的逻辑:通过添加'slide--current'类名来实现透明度变为1,展示在页面上。
1、所有代码都是自执行函数,先实现两个方法,方便之后使用。
; (function (window) {
// 严格模式下
'use strict';
// extend:把某一个对象的属性复制给另一个对象,把b的属性给a
function extend(a, b) {
for (var key in b) {
if (b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
return a;
}
// 防抖:我们不希望当快速点击的时候我们就一直在执行。想要间隔一定的时间在去触发
function debounce(func, wait) {
var timeout;
return function () {
var context = this, args = arguments;
var later = function () {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
})(window)
2、实现风格切换的方法:
function MLSlideshow(el, options) {
// 1. 元素属性
// 拿到元素,就把元素挂载到对象下
this.el = el;
// 2. 相关参数
this.options = extend({}, this.options);
extend(this.options, options);
// 3. 所有照片的风格
// 最外层盒子取到所有风格的类名
this.slides = [].slice.call(this.el.querySelectorAll('.slide'));
// 判断:如果当前照片风格少于2,不创建实例————也就是只有1种风格
this.slidesTotal = this.slides.length;
if (this.slidesTotal <= 1) {
return;
}
// 4. 当前展示的风格的下标
this.current = this.options.startIdx || 0;
// 5. 当前窗口大小————因为图片的宽高是由窗口大小决定的
this.dimentions = {
width: this.el.offsetWidth,
height: this.el.offsetHeight
}
// 6. 照片风格初始化
// 需要一个_init方法————下面会写
this._init();
}
解释:
this.slides = [].slice.call(this.el.querySelectorAll('.slide'));
如下图index.html中知道每个风格的类名都叫 slide ,this.el.querySelectorAll('.slide')返回的是一个伪数组数据,所以我们需要转化为正常的数组来方便之后处理,通过slice
3、风格初始化——_init
// 照片墙初始化
MLSlideshow.prototype._init = function () {
var self = this, // self保存一下当前的this
// 1. 当图片加载完成,展示当前第一个风格————由current决定是第几个风格
// onPreload:图片加载完成要做的事情
onPreload = function () {
// 给当前的图片添加一个类名
self.el.classList.add('slideshow--loaded');
// 让第一个风格展示
self.slides[self.current].classList.add('slide--current');
};
// 在图片加载完成的时候调用
this._preload(onPreload);
// 2. 添加事件:
// 2.1 窗口大小事件
// 2.2 键盘触发切换风格事件
// _initEvents独立出一个方法
this._initEvents();
}
解释:
当图片加载完成之后,才会展示当前第一个风格,那么我们会用到一个插件:
这个插件里面通提供了一个方法imagesLoaded,来判断我们的图片是否加载完成。
// 判断图片是否加载完成
MLSlideshow.prototype._preload = function (callback) {
// imagesLoaded直接调用,因为我们已经引入了这个包
imagesLoaded(this.el, { background: true }, function () {
// 如果传入的回调是一个函数,我们就去调用它
if (typeof callback === 'function') {
callback();
}
});
};
4、添加事件——_initEvents:
MLSlideshow.prototype._initEvents = function () {
var self = this;
// 2.1 窗口大小事件
// 用到防抖debounce
this.debounceResize = debounce(function (ev) {
self.dimentions = {
width: self.el.offsetWidth,
height: self.el.offsetHeight
};
}, 10)
// 添加事件,当窗口的大小发送改变:
window.addEventListener('resize', this.debounceResize);
// 2.2 键盘触发切换风格事件
// 按下左右键的键盘按键,触发切换的按钮
this.keyboardFn = function (ev) {
var keyCode = ev.keyCode || ev.which;
switch (keyCode) {
case 37:
// _navigate('prev'):往前切换————一会完成前后切换的函数实现
self._navigate('prev');
break;
case 39:
// _navigate('next'):往后切换
self._navigate('next');
break;
}
};
this.el.addEventListener('keydown', this.keyboardFn);
}
5、前后切换——_navigate
// _navigate:参数接收prev和next
MLSlideshow.prototype._navigate = function (direction) {
// isAnimating:判断当前动画是否在运动
if (this.isAnimating) {
return false;
}
this.isAnimating = true;
// 1. 获取当前展示风格的信息: 当前元素,唯一标识,相关标题
var self = this,
// 知道当前展示的下标是什么,当点击前后切换的时候,改变下标的值来完成
currentSlide = this.slides[this.current],
// 获取当前风格标识
currentLayout = currentSlide.getAttribute('data-layout') || 'layout1',
// 获取当前展示风格的标题
currentTitle = currentSlide.querySelector('.slide__title');
// 2. 获取下一个要展示风格的信息: 当前元素,唯一标识,相关标题
if (direction === 'next') {
this.current = this.current < this.slidesTotal - 1 ? this.current + 1 : 0;
}
else {
this.current = this.current > 0 ? this.current - 1 : this.slidesTotal - 1;
}
// 获取元素信息
var nextSlide = this.slides[this.current],
nextLayout = nextSlide.getAttribute('data-layout'),
nextTitle = nextSlide.querySelector('.slide__title');
// 3. 针对要出去的元素的动画
// currentSlide.querySelectorAll('.slide-imgwrap .slide__img-inner'):动画中要出去的所有的图片
// currentSlide:当前要出去的元素
var outItems = [].slice.call(currentSlide.querySelectorAll('.slide-imgwrap .slide__img-inner')),
// 获取相关配置参数
outconfig = this.options.layoutConfig[currentLayout] !== undefined ? this.options.layoutConfig[currentLayout].out : this.options.layoutConfig['layout1'].out,
animeOutProps = {
targets: outItems,
duration: outconfig.duration,
easing: outconfig.easing,
// 延迟是一个方法
delay: function (el, index) {
return direction === 'next' ? index * outconfig.itemsDelay : (outItems.length - 1 - index) * outconfig.itemsDelay;
},
// 走的这个元素之前是添加了一个类名,现在走了要把类名拿掉
complete: function () {
currentSlide.classList.remove('slide--current');
}
};
this._setAnimationProperties(animeOutProps, outconfig, direction)
// 参数传入,直接进行动画
anime(animeOutProps);
// 标题
this._anmateTitle(currentTitle, 'out');
// 创建定时器:当前风格出去了,再让下一个风格进来
clearTimeout(this.navtime);
// animateIn:让动画进来
this.navtime = setTimeout(animateIn, this.options.layoutConfig[nextLayout] !== undefined && this.options.layoutConfig[nextLayout].in.delay !== undefined ? this.options.layoutConfig[nextLayout].in.delay : 150);
}
针对要显示的风格创建方法:
// _navigate:参数接收prev和next
MLSlideshow.prototype._navigate = function (direction) {
// isAnimating:判断当前动画是否在运动
if (this.isAnimating) {
return false;
}
this.isAnimating = true;
// 1. 获取当前展示风格的信息: 当前元素,唯一标识,相关标题
var self = this,
// 知道当前展示的下标是什么也就是要出去的元素,当点击前后切换的时候,改变下标的值来完成
currentSlide = this.slides[this.current],
// 获取当前风格标识
currentLayout = currentSlide.getAttribute('data-layout') || 'layout1',
// 获取当前展示风格的标题
currentTitle = currentSlide.querySelector('.slide__title');
// 2. 获取下一个要展示风格的信息: 当前元素,唯一标识,相关标题
if (direction === 'next') {
this.current = this.current < this.slidesTotal - 1 ? this.current + 1 : 0;
}
else {
this.current = this.current > 0 ? this.current - 1 : this.slidesTotal - 1;
}
// 获取元素信息
var nextSlide = this.slides[this.current],
nextLayout = nextSlide.getAttribute('data-layout'),
nextTitle = nextSlide.querySelector('.slide__title');
// 4. 针对要显示的风格创建方法
var animateIn = function () {
// 开启动画之前把其他动画停掉
clearTimeout(self.navtime);
// 获取计算的要进入的风格的照片元素
var inItems = [].slice.call(nextSlide.querySelectorAll('.slide-imgwrap .slide__img-inner')),
// 设置计算的下一个的下标元素动画,如果不存在就设置第一个元素动画,标识区分
// 获取即将进入的风格的动画参数
inconfig = self.options.layoutConfig[nextLayout] !== undefined ? self.options.layoutConfig[nextLayout].in : self.options.layoutConfig['layout1'].in,
// 进入时,初始化设置赋值给inresetconfig
inresetconfig = inconfig.resetProps,
// 设置动画参数
animeInProps = {
targets: inItems,
duration: inconfig.duration,
easing: inconfig.easing,
delay: function (el, index) {
return direction === 'next' ? index * inconfig.itemsDelay : (inItems.length - 1 - index) * inconfig.itemsDelay;
},
// 动画完成设置动画运动标识是false
complete: function () {
self.isAnimating = false;
}
};
// Configure the animation in properties.
// 将动画参数,当前动画元素相关信息,前后传入设置动画属性的方法中
self._setAnimationProperties(animeInProps, inconfig, direction);
// Reset before animating in:
// 在动画之前重置即将进入的风格的图片
// 需要重置的原因:因为图片的宽高是相对窗口的 ,但是图片的宽高又是根据浏览器窗口的,所以每一次我们都要重置一下图片的宽高,避免窗口发生改变之后,盒子小了图片还很大这种不好的效果
inItems.forEach(function (item, pos) {
var transformStr = '';
// 将属性中的translateX、translateY、rotateZ、scale、opacity进行重置
if (inresetconfig.translateX !== undefined) {
var tx = typeof inresetconfig.translateX === 'object' ?
function () {
return typeof inresetconfig.translateX[direction] === 'function' ?
self._getValuePercentage(inresetconfig.translateX[direction](item, pos), 'width') :
self._getValuePercentage(inresetconfig.translateX[direction], 'width');
} : self._getValuePercentage(inresetconfig.translateX, 'width');
transformStr += ' translateX(' + (typeof tx === 'function' ? tx() : tx) + 'px)';
}
if (inresetconfig.translateY !== undefined) {
var ty = typeof inresetconfig.translateY === 'object' ? function () {
return typeof inresetconfig.translateY[direction] === 'function' ? self._getValuePercentage(inresetconfig.translateY[direction](item, pos), 'height') : self._getValuePercentage(inresetconfig.translateY[direction], 'height');
} : self._getValuePercentage(inresetconfig.translateY, 'height');
transformStr += ' translateY(' + (typeof ty === 'function' ? ty() : ty) + 'px)';
}
if (inresetconfig.rotateZ !== undefined) {
var rot = typeof inresetconfig.rotateZ === 'object' ? function () {
return typeof inresetconfig.rotateZ[direction] === 'function' ? inresetconfig.rotateZ[direction](item, pos) : inresetconfig.rotateZ[direction];
} : inresetconfig.rotateZ;
transformStr += ' rotateZ(' + (typeof rot === 'function' ? rot() : rot) + 'deg)';
}
if (inresetconfig.scale !== undefined) {
var s = typeof inresetconfig.scale === 'object' ? function () {
return typeof inresetconfig.scale[direction] === 'function' ? inresetconfig.scale[direction](item, pos) : inresetconfig.scale[direction];
} : inresetconfig.scale;
transformStr += ' scale(' + (typeof s === 'function' ? s() : s) + ')';
}
if (transformStr !== '') {
item.style.transform = item.style.WebkitTransform = transformStr;
}
if (inresetconfig.opacity !== undefined) {
item.style.opacity = inresetconfig.opacity;
}
});
// 设置即将进入的风格的title是透明的
// Reset next title.
nextTitle.style.opacity = 0;
// Switch current class.
// 设置即将进入的元素类名是当前风格的
nextSlide.classList.add('slide--current');
// Animate next slide in.
// 动画让其进入
anime(animeInProps);
// Animate next title in.
// 让标题进入
self._animateTitle(nextTitle, 'in');
};
// 3. 针对要出去的元素的动画
// currentSlide.querySelectorAll('.slide-imgwrap .slide__img-inner'):动画中要出去的所有的图片
// currentSlide:当前要出去的元素
var outItems = [].slice.call(currentSlide.querySelectorAll('.slide-imgwrap .slide__img-inner')),
// 获取相关配置参数
outconfig = this.options.layoutConfig[currentLayout] !== undefined ? this.options.layoutConfig[currentLayout].out : this.options.layoutConfig['layout1'].out,
animeOutProps = {
targets: outItems,
duration: outconfig.duration,
easing: outconfig.easing,
// 延迟是一个方法
delay: function (el, index) {
return direction === 'next' ? index * outconfig.itemsDelay : (outItems.length - 1 - index) * outconfig.itemsDelay;
},
// 走的这个元素之前是添加了一个类名,现在走了要把类名拿掉
complete: function () {
currentSlide.classList.remove('slide--current');
}
};
this._setAnimationProperties(animeOutProps, outconfig, direction)
// 参数传入,直接进行动画
anime(animeOutProps);
// 标题
this._anmateTitle(currentTitle, 'out');
// 创建定时器:当前风格出去了,再让下一个风格进来
clearTimeout(this.navtime);
// animateIn:让动画进来
this.navtime = setTimeout(animateIn, this.options.layoutConfig[nextLayout] !== undefined && this.options.layoutConfig[nextLayout].in.delay !== undefined ? this.options.layoutConfig[nextLayout].in.delay : 150);
}
6、图片进入和出去相关的动画参数——options
这里有7个动画效果,我们只分析一个,其他7个是完全一样的,详细看源码。
// 图片进入和出去相关的动画参数
// 设计风格动画参数
MLSlideshow.prototype.options = {
// 起始的下标设置成0
startIdx: 0,
// layoutConfig:7个风格会放在一个对象下
layoutConfig: {
layout1: {
// 退出时
out: {
// 点击next还是点击prev,方向是不一样的
translateX: {
next: '-100%',
prev: '100%'
},
rotateZ: {
next: function (el, index) {
return anime.random(-15, 0);
},
prev: function (el, index) {
return anime.random(0, 15);
}
},
opacity: 0,
duration: 1200,
easing: 'easeOutQuint',
itemsDelay: 80
},
// 进入时
in: {
// resetProps:在进入时需要重置一下动画的参数
resetProps: {
translateX: {
next: '100%',
prev: '-100%'
},
rotateZ: {
next: function (el, index) {
return anime.random(0, 15);
},
prev: function (el, index) {
return anime.random(-15, 0);
}
},
opacity: 0,
},
translateX: '0%',
rotateZ: 0,
opacity: 1,
duration: 700,
easing: 'easeOutQuint',
itemsDelay: 80
}
},
layout12: {
}
...
}
}
7、进入和出去两个动画之前都是处理一下参数,把config的参数传递给当前动画的参数,思路和初始化的思路是一样的。
// 处理参数
MLSlideshow.prototype._setAnimationProperties = function (props, config, direction) {
var self = this;
if (config.translateX !== undefined) {
props.translateX = typeof config.translateX === 'object' ?
function (el, index) {
return typeof config.translateX[direction] === 'function' ?
self._getValuePercentage(config.translateX[direction](el, index), 'width')
: self._getValuePercentage(config.translateX[direction], 'width');
} : this._getValuePercentage(config.translateX, 'width');
}
if (config.translateY !== undefined) {
props.translateY = typeof config.translateY === 'object' ? function (el, index) {
return typeof config.translateY[direction] === 'function' ? self._getValuePercentage(config.translateY[direction](el, index), 'width') : self._getValuePercentage(config.translateY[direction], 'height');
} : this._getValuePercentage(config.translateY, 'height');
}
if (config.rotateZ !== undefined) {
props.rotateZ = typeof config.rotateZ === 'object' ? function (el, index) {
return typeof config.rotateZ[direction] === 'function' ? config.rotateZ[direction](el, index) : config.rotateZ[direction];
} : config.rotateZ;
}
if (config.scale !== undefined) {
props.scale = typeof config.scale === 'object' ? function (el, index) {
return typeof config.scale[direction] === 'function' ? config.scale[direction](el, index) : config.scale[direction];
} : config.scale;
}
if (config.opacity !== undefined) {
props.opacity = config.opacity;
}
};
// _getValuePercentage :获取百分比,改为值
MLSlideshow.prototype._getValuePercentage = function (str, axis) {
return typeof str === 'string' && str.indexOf('%') !== -1 ? parseFloat(str) / 100 * this.dimentions[axis] : str;
}
8、设置title动画
// 设置title动画
MLSlideshow.prototype._anmateTitle = function (titleEl, dir) {
anime({
targets: titleEl,
opacity: dir === 'out' ? 0 : 1,
duration: dir === 'out' ? 200 : 500,
easing: 'easeOutExpo'
})
}
MLSlideshow.prototype.next = function () {
this._navigate('next');
}
MLSlideshow.prototype.prev = function () {
this._navigate('prev');
}
window.MLSlideshow = MLSlideshow;
代码就完成了,我们去index.html调用我们写的方法:
<script src="js/imagesloaded.pkgd.min.js"></script>
<script src="js/anime.min.js"></script>
<script src="js/main.js"></script>
<script>
(function () {
var slideshow = new MLSlideshow(document.querySelector('.slideshow'));
document.querySelector('#prev-slide').addEventListener('click', function () {
slideshow.prev();
})
document.querySelector('#next-slide').addEventListener('click', function () {
slideshow.next();
})
})()
</script>
三、完整代码:
// ▷main.js
// 所有代码都是自执行函数
; (function (window) {
// 严格模式下
'use strict';
// extend:把某一个对象的属性复制给另一个对象,把b的属性给a
function extend(a, b) {
for (var key in b) {
if (b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
return a;
}
// 防抖:我们不希望当快速点击的时候我们就一直在执行。想要间隔一定的时间在去触发
function debounce(func, wait) {
var timeout;
return function () {
var context = this, args = arguments;
var later = function () {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
function MLSlideshow(el, options) {
// 1. 元素属性
// 拿到元素,就把元素挂载到对象下
this.el = el;
// 2. 相关参数
this.options = extend({}, this.options);
extend(this.options, options);
// 3. 所有照片的风格
// 最外层盒子取到所有风格的类名
this.slides = [].slice.call(this.el.querySelectorAll('.slide'));
// 判断:如果当前照片风格少于2,不创建实例
this.slidesTotal = this.slides.length;
if (this.slidesTotal <= 1) {
return;
}
// 4. 当前展示的风格的下标
this.current = this.options.startIdx || 0;
// 5. 当前窗口
this.dimentions = {
width: this.el.offsetWidth,
height: this.el.offsetHeight
}
// 6. 照片风格初始化
this._init();
}
// 照片墙初始化
MLSlideshow.prototype._init = function () {
var self = this, // self保存一下当前的this
// 1. 当图片加载完成,展示当前第一个风格————由current决定是第几个风格
// onPreload:图片加载完成要做的事情
onPreload = function () {
// 给当前的图片添加一个类名
self.el.classList.add('slideshow--loaded');
// 让第一个风格展示
self.slides[self.current].classList.add('slide--current');
};
// 在图片加载完成的时候调用
this._preload(onPreload);
// 2. 添加事件:
// _initEvents独立出一个方法
this._initEvents();
}
// 判断图片是否加载完成
MLSlideshow.prototype._preload = function (callback) {
// imagesLoaded直接调用,因为我们已经引入了这个包
imagesLoaded(this.el, { background: true }, function () {
// 如果传入的回调是一个函数,我们就去调用它
if (typeof callback === 'function') {
callback();
}
});
};
MLSlideshow.prototype._initEvents = function () {
var self = this;
// 2.1 窗口大小事件
// 用到防抖debounce
this.debounceResize = debounce(function (ev) {
self.dimentions = {
width: self.el.offsetWidth,
height: self.el.offsetHeight
};
}, 10)
// 添加事件,当窗口的大小发送改变:
window.addEventListener('resize', this.debounceResize);
// 2.2 键盘触发切换风格事件
// 按下左右键的键盘按键,触发切换的按钮
this.keyboardFn = function (ev) {
var keyCode = ev.keyCode || ev.which;
switch (keyCode) {
case 37:
// _navigate('prev'):往前切换
self._navigate('prev');
break;
case 39:
// _navigate('next'):往后切换
self._navigate('next');
break;
}
};
this.el.addEventListener('keydown', this.keyboardFn);
}
// 图片进入和出去相关的动画参数
// 设计风格动画参数
MLSlideshow.prototype.options = {
// 起始的下标设置成0
startIdx: 0,
// layoutConfig:7个风格会放在一个对象下
layoutConfig: {
layout1: {
// 退出时
out: {
// 点击next还是点击prev,方向是不一样的
translateX: {
next: '-100%',
prev: '100%'
},
rotateZ: {
next: function (el, index) {
return anime.random(-15, 0);
},
prev: function (el, index) {
return anime.random(0, 15);
}
},
opacity: 0,
duration: 1200,
easing: 'easeOutQuint',
itemsDelay: 80
},
// 进入时
in: {
// resetProps:在进入时需要重置一下动画的参数
resetProps: {
translateX: {
next: '100%',
prev: '-100%'
},
rotateZ: {
next: function (el, index) {
return anime.random(0, 15);
},
prev: function (el, index) {
return anime.random(-15, 0);
}
},
opacity: 0,
},
translateX: '0%',
rotateZ: 0,
opacity: 1,
duration: 700,
easing: 'easeOutQuint',
itemsDelay: 80
}
},
layout2: {
out: {
translateX: {
next: function (el, index) {
return anime.random(-50, 50) + '%';
},
prev: function (el, index) {
return anime.random(-50, 50) + '%';
}
},
translateY: {
next: function (el, index) {
return anime.random(-50, 50) + '%';
},
prev: function (el, index) {
return anime.random(-50, 50) + '%';
}
},
opacity: 0,
duration: 1200,
easing: 'easeOutQuint',
itemsDelay: 10
},
in: {
resetProps: {
translateX: {
next: '100%',
prev: '-100%'
},
rotateZ: {
next: function (el, index) {
return anime.random(0, 90);
},
prev: function (el, index) {
return anime.random(-90, 0);
}
},
opacity: 0,
},
translateX: '0%',
rotateZ: 0,
opacity: 1,
duration: 900,
easing: 'easeOutExpo',
itemsDelay: 30
}
},
layout3: {
out: {
translateX: '-10%',
rotateZ: 0,
opacity: 0,
duration: 500,
easing: 'easeOutExpo',
itemsDelay: 0
},
in: {
resetProps: {
translateX: '-10%',
rotateZ: 0,
opacity: 0
},
translateX: 0,
opacity: 1,
rotateZ: {
next: function (el, index) {
return index * 6;
},
prev: function (el, index) {
return index * 6;
}
},
duration: 1200,
easing: 'easeOutElastic',
itemsDelay: 0
}
},
layout4: {
out: {
translateY: {
next: '60%',
prev: '-60%'
},
opacity: 0,
duration: 700,
easing: 'easeOutQuint',
itemsDelay: 50
},
in: {
resetProps: {
translateY: {
next: '-60%',
prev: '60%'
},
opacity: 0,
},
translateY: '0%',
opacity: 1,
duration: 700,
easing: 'easeOutQuint',
itemsDelay: 50,
delay: 250
}
},
layout5: {
out: {
scale: 0.5,
opacity: 0,
duration: 500,
easing: 'easeOutExpo',
itemsDelay: 20
},
in: {
resetProps: {
scale: 0.5,
opacity: 0
},
opacity: 1,
scale: 1,
duration: 500,
easing: 'easeOutExpo',
itemsDelay: 20,
delay: 300
}
},
layout6: {
out: {
scale: 0.5,
opacity: 0,
duration: 300,
easing: 'easeInBack',
itemsDelay: 20
},
in: {
resetProps: {
scale: 0.5,
opacity: 0
},
opacity: 1,
scale: 1,
duration: 1000,
easing: 'easeOutElastic',
itemsDelay: 50,
delay: 400
}
},
layout7: {
out: {
translateX: {
next: '-100%',
prev: '100%'
},
opacity: 0,
duration: 1200,
easing: 'easeOutQuint',
itemsDelay: 40
},
in: {
resetProps: {
translateX: {
next: '100%',
prev: '-100%'
},
rotateZ: {
next: function (el, index) {
return anime.random(0, 25);
},
prev: function (el, index) {
return anime.random(-25, 0);
}
},
opacity: 0,
},
translateX: '0%',
rotateZ: 0,
opacity: 1,
duration: 700,
easing: 'easeOutQuint',
itemsDelay: 40,
delay: 250
}
}
}
};
// _navigate:参数接收prev和next
MLSlideshow.prototype._navigate = function (direction) {
// isAnimating:判断当前动画是否在运动
if (this.isAnimating) {
return false;
}
this.isAnimating = true;
// 1. 获取当前展示风格的信息: 当前元素,唯一标识,相关标题
var self = this,
// 知道当前展示的下标是什么也就是要出去的元素,当点击前后切换的时候,改变下标的值来完成
currentSlide = this.slides[this.current],
// 获取当前风格标识
currentLayout = currentSlide.getAttribute('data-layout') || 'layout1',
// 获取当前展示风格的标题
currentTitle = currentSlide.querySelector('.slide__title');
// 2. 获取下一个要展示风格的信息: 当前元素,唯一标识,相关标题
if (direction === 'next') {
this.current = this.current < this.slidesTotal - 1 ? this.current + 1 : 0;
}
else {
this.current = this.current > 0 ? this.current - 1 : this.slidesTotal - 1;
}
// 获取元素信息
var nextSlide = this.slides[this.current],
nextLayout = nextSlide.getAttribute('data-layout'),
nextTitle = nextSlide.querySelector('.slide__title');
// 4. 针对要显示的风格创建方法
var animateIn = function () {
// 开启动画之前把其他动画停掉
clearTimeout(self.navtime);
// 获取计算的要进入的风格的照片元素
var inItems = [].slice.call(nextSlide.querySelectorAll('.slide-imgwrap .slide__img-inner')),
// 设置计算的下一个的下标元素动画,如果不存在就设置第一个元素动画,标识区分
// 获取即将进入的风格的动画参数
inconfig = self.options.layoutConfig[nextLayout] !== undefined ? self.options.layoutConfig[nextLayout].in : self.options.layoutConfig['layout1'].in,
// 进入时,初始化设置赋值给inresetconfig
inresetconfig = inconfig.resetProps,
// 设置动画参数
animeInProps = {
targets: inItems,
duration: inconfig.duration,
easing: inconfig.easing,
delay: function (el, index) {
return direction === 'next' ? index * inconfig.itemsDelay : (inItems.length - 1 - index) * inconfig.itemsDelay;
},
// 动画完成设置动画运动标识是false
complete: function () {
self.isAnimating = false;
}
};
// Configure the animation in properties.
// 将动画参数,当前动画元素相关信息,前后传入设置动画属性的方法中
self._setAnimationProperties(animeInProps, inconfig, direction);
// Reset before animating in:
// 在动画之前重置即将进入的风格的图片
// 需要重置的原因:因为图片的宽高是相对窗口的 ,但是图片的宽高又是根据浏览器窗口的,所以每一次我们都要重置一下图片的宽高,避免窗口发生改变之后,盒子小了图片还很大这种不好的效果
inItems.forEach(function (item, pos) {
var transformStr = '';
// 将属性中的translateX、translateY、rotateZ、scale、opacity进行重置
if (inresetconfig.translateX !== undefined) {
var tx = typeof inresetconfig.translateX === 'object' ?
function () {
return typeof inresetconfig.translateX[direction] === 'function' ?
self._getValuePercentage(inresetconfig.translateX[direction](item, pos), 'width') :
self._getValuePercentage(inresetconfig.translateX[direction], 'width');
} : self._getValuePercentage(inresetconfig.translateX, 'width');
transformStr += ' translateX(' + (typeof tx === 'function' ? tx() : tx) + 'px)';
}
if (inresetconfig.translateY !== undefined) {
var ty = typeof inresetconfig.translateY === 'object' ? function () {
return typeof inresetconfig.translateY[direction] === 'function' ? self._getValuePercentage(inresetconfig.translateY[direction](item, pos), 'height') : self._getValuePercentage(inresetconfig.translateY[direction], 'height');
} : self._getValuePercentage(inresetconfig.translateY, 'height');
transformStr += ' translateY(' + (typeof ty === 'function' ? ty() : ty) + 'px)';
}
if (inresetconfig.rotateZ !== undefined) {
var rot = typeof inresetconfig.rotateZ === 'object' ? function () {
return typeof inresetconfig.rotateZ[direction] === 'function' ? inresetconfig.rotateZ[direction](item, pos) : inresetconfig.rotateZ[direction];
} : inresetconfig.rotateZ;
transformStr += ' rotateZ(' + (typeof rot === 'function' ? rot() : rot) + 'deg)';
}
if (inresetconfig.scale !== undefined) {
var s = typeof inresetconfig.scale === 'object' ? function () {
return typeof inresetconfig.scale[direction] === 'function' ? inresetconfig.scale[direction](item, pos) : inresetconfig.scale[direction];
} : inresetconfig.scale;
transformStr += ' scale(' + (typeof s === 'function' ? s() : s) + ')';
}
if (transformStr !== '') {
item.style.transform = item.style.WebkitTransform = transformStr;
}
if (inresetconfig.opacity !== undefined) {
item.style.opacity = inresetconfig.opacity;
}
});
// 设置即将进入的风格的title是透明的
// Reset next title.
nextTitle.style.opacity = 0;
// Switch current class.
// 设置即将进入的元素类名是当前风格的
nextSlide.classList.add('slide--current');
// Animate next slide in.
// 动画让其进入
anime(animeInProps);
// Animate next title in.
// 让标题进入
self._anmateTitle(nextTitle, 'in');
};
// 3. 针对要出去的元素的动画
// currentSlide.querySelectorAll('.slide-imgwrap .slide__img-inner'):动画中要出去的所有的图片
// currentSlide:当前要出去的元素
var outItems = [].slice.call(currentSlide.querySelectorAll('.slide-imgwrap .slide__img-inner')),
// 获取相关配置参数
outconfig = this.options.layoutConfig[currentLayout] !== undefined ? this.options.layoutConfig[currentLayout].out : this.options.layoutConfig['layout1'].out,
animeOutProps = {
targets: outItems,
duration: outconfig.duration,
easing: outconfig.easing,
// 延迟是一个方法
delay: function (el, index) {
return direction === 'next' ? index * outconfig.itemsDelay : (outItems.length - 1 - index) * outconfig.itemsDelay;
},
// 走的这个元素之前是添加了一个类名,现在走了要把类名拿掉
complete: function () {
currentSlide.classList.remove('slide--current');
}
};
this._setAnimationProperties(animeOutProps, outconfig, direction)
// 参数传入,直接进行动画
anime(animeOutProps);
// 标题
this._anmateTitle(currentTitle, 'out');
// 创建定时器:当前风格出去了,再让下一个风格进来
clearTimeout(this.navtime);
// animateIn:让动画进来
this.navtime = setTimeout(animateIn, this.options.layoutConfig[nextLayout] !== undefined && this.options.layoutConfig[nextLayout].in.delay !== undefined ? this.options.layoutConfig[nextLayout].in.delay : 150);
}
// 处理参数
MLSlideshow.prototype._setAnimationProperties = function (props, config, direction) {
var self = this;
if (config.translateX !== undefined) {
props.translateX = typeof config.translateX === 'object' ?
function (el, index) {
return typeof config.translateX[direction] === 'function' ?
self._getValuePercentage(config.translateX[direction](el, index), 'width')
: self._getValuePercentage(config.translateX[direction], 'width');
} : this._getValuePercentage(config.translateX, 'width');
}
if (config.translateY !== undefined) {
props.translateY = typeof config.translateY === 'object' ? function (el, index) {
return typeof config.translateY[direction] === 'function' ? self._getValuePercentage(config.translateY[direction](el, index), 'width') : self._getValuePercentage(config.translateY[direction], 'height');
} : this._getValuePercentage(config.translateY, 'height');
}
if (config.rotateZ !== undefined) {
props.rotateZ = typeof config.rotateZ === 'object' ? function (el, index) {
return typeof config.rotateZ[direction] === 'function' ? config.rotateZ[direction](el, index) : config.rotateZ[direction];
} : config.rotateZ;
}
if (config.scale !== undefined) {
props.scale = typeof config.scale === 'object' ? function (el, index) {
return typeof config.scale[direction] === 'function' ? config.scale[direction](el, index) : config.scale[direction];
} : config.scale;
}
if (config.opacity !== undefined) {
props.opacity = config.opacity;
}
};
MLSlideshow.prototype._getValuePercentage = function (str, axis) {
return typeof str === 'string' && str.indexOf('%') !== -1 ? parseFloat(str) / 100 * this.dimentions[axis] : str;
}
// 设置title动画
MLSlideshow.prototype._anmateTitle = function (titleEl, dir) {
anime({
targets: titleEl,
opacity: dir === 'out' ? 0 : 1,
duration: dir === 'out' ? 200 : 500,
easing: 'easeOutExpo'
})
}
MLSlideshow.prototype.next = function () {
this._navigate('next');
}
MLSlideshow.prototype.prev = function () {
this._navigate('prev');
}
window.MLSlideshow = MLSlideshow;
})(window)
// ▷index.html
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>照片墙</title>
<link rel="stylesheet" type="text/css" href="css/normalize.css" />
<link rel="stylesheet" type="text/css" href="css/demo.css" />
<link rel="stylesheet" type="text/css" href="css/slideshow.css" />
<link rel="stylesheet" type="text/css" href="css/slideshow_layouts.css" />
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<style>
.ie-message { display: inline-block; }
</style>
<![endif]-->
<script>document.documentElement.className = 'js';</script>
</head>
<body>
<svg class="hidden">
<defs>
<symbol id="icon-arrow" viewBox="0 0 24 24">
<title>arrow</title>
<polygon points="6.3,12.8 20.9,12.8 20.9,11.2 6.3,11.2 10.2,7.2 9,6 3.1,12 9,18 10.2,16.8 " />
</symbol>
<symbol id="icon-drop" viewBox="0 0 24 24">
<title>drop</title>
<path
d="M12,21c-3.6,0-6.6-3-6.6-6.6C5.4,11,10.8,4,11.4,3.2C11.6,3.1,11.8,3,12,3s0.4,0.1,0.6,0.3c0.6,0.8,6.1,7.8,6.1,11.2C18.6,18.1,15.6,21,12,21zM12,4.8c-1.8,2.4-5.2,7.4-5.2,9.6c0,2.9,2.3,5.2,5.2,5.2s5.2-2.3,5.2-5.2C17.2,12.2,13.8,7.3,12,4.8z" />
<path
d="M12,18.2c-0.4,0-0.7-0.3-0.7-0.7s0.3-0.7,0.7-0.7c1.3,0,2.4-1.1,2.4-2.4c0-0.4,0.3-0.7,0.7-0.7c0.4,0,0.7,0.3,0.7,0.7C15.8,16.5,14.1,18.2,12,18.2z" />
</symbol>
<symbol id="icon-prev" viewBox="0 0 100 50">
<title>prev</title>
<polygon
points="5.4,25 18.7,38.2 22.6,34.2 16.2,27.8 94.6,27.8 94.6,22.2 16.2,22.2 22.6,15.8 18.7,11.8" />
</symbol>
<symbol id="icon-next" viewBox="0 0 100 50">
<title>next</title>
<polygon
points="81.3,11.8 77.4,15.8 83.8,22.2 5.4,22.2 5.4,27.8 83.8,27.8 77.4,34.2 81.3,38.2 94.6,25 " />
</symbol>
<symbol id="icon-octicon" viewBox="0 0 24 24">
<title>octicon</title>
<path
d="M12,2.2C6.4,2.2,1.9,6.7,1.9,12.2c0,4.4,2.9,8.2,6.9,9.6c0.5,0.1,0.7-0.2,0.7-0.5c0-0.2,0-0.9,0-1.7c-2.8,0.6-3.4-1.4-3.4-1.4C5.6,17.1,5,16.8,5,16.8C4.1,16.2,5,16.2,5,16.2c1,0.1,1.5,1,1.5,1c0.9,1.5,2.4,1.1,2.9,0.8c0.1-0.7,0.4-1.1,0.6-1.3c-2.2-0.3-4.6-1.1-4.6-5c0-1.1,0.4-2,1-2.7C6.5,8.8,6.2,7.7,6.7,6.4c0,0,0.8-0.3,2.8,1C10.3,7.2,11.1,7.1,12,7c0.9,0,1.7,0.1,2.5,0.3c1.9-1.3,2.8-1,2.8-1c0.5,1.4,0.2,2.4,0.1,2.7c0.6,0.7,1,1.6,1,2.7c0,3.9-2.4,4.7-4.6,5c0.4,0.3,0.7,0.9,0.7,1.9c0,1.3,0,2.4,0,2.8c0,0.3,0.2,0.6,0.7,0.5c4-1.3,6.9-5.1,6.9-9.6C22.1,6.7,17.6,2.2,12,2.2z" />
</symbol>
<clipPath id="polygon-clip-rhomboid" clipPathUnits="objectBoundingBox">
<polygon points="0 1, 0.3 0, 1 0, 0.7 1" />
</clipPath>
</defs>
</svg>
<main>
<div class="slideshow" tabindex="0">
<div class="slide slide--layout-1" data-layout="layout1">
<div class="slide-imgwrap">
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/1.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/2.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/3.jpg);"></div>
</div>
</div>
<div class="slide__title">
<h3 class="slide__title-main">Now or Never</h3>
<p class="slide__title-sub">Our battered suitcases were piled on the sidewalk again; we had longer
ways to go. But no matter, the road is life. <a href="#">Read more</a></p>
</div>
</div>
<div class="slide slide--layout-2" data-layout="layout2">
<div class="slide-imgwrap">
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/6.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/5.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/6.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/7.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/9.jpg);">
<h4 class="slide__img-caption">Today is someday</h4>
</div>
</div>
</div>
<div class="slide__title">
<h3 class="slide__title-main">Crazy Breed</h3>
<p class="slide__title-sub">There's those thinking more or less less is more. But if less is more
how you're keeping score?</p>
</div>
</div>
<div class="slide slide--layout-3" data-layout="layout3">
<div class="slide-imgwrap">
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/9.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/10.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/11.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/15.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/13.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/14.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/12.jpg);"></div>
</div>
</div>
<div class="slide__title">
<h3 class="slide__title-main">Safe Harbor</h3>
<p class="slide__title-sub">Twenty years from now you will be more disappointed by the things you
didn’t do than by the ones you did do.</p>
</div>
</div>
<div class="slide slide--layout-4" data-layout="layout4">
<div class="slide-imgwrap">
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/10.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/8.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/11.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/13.jpg);"></div>
</div>
</div>
<div class="slide__title">
<h3 class="slide__title-main">Our Freedom</h3>
<p class="slide__title-sub">For to be free is not merely to cast off one's chains, but to live in a
way that respects and enhances the freedom of others.</p>
</div>
</div>
<div class="slide slide--layout-5" data-layout="layout5">
<div class="slide-imgwrap">
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/1.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/2.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/3.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/4.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/5.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/6.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/7.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/8.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/9.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/10.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/11.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/12.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/13.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/14.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/15.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/16.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/17.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/small/18.jpg);"></div>
</div>
</div>
<div class="slide__title">
<h3 class="slide__title-main">Stopping Time</h3>
<p class="slide__title-sub">Emancipate yourselves from mental slavery, none but ourselves can free
our minds.</p>
</div>
</div>
<div class="slide slide--layout-6" data-layout="layout6">
<div class="slide-imgwrap">
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/14.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/11.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/3.jpg);"></div>
</div>
</div>
<div class="slide__title">
<h3 class="slide__title-main">Walk the Walk</h3>
<p class="slide__title-sub">The trouble with being in the rat race is that even if you win, you're
still a rat.</p>
</div>
</div>
<div class="slide slide--layout-7" data-layout="layout7">
<div class="slide-imgwrap">
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/16.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/1.jpg);"></div>
</div>
<div class="slide__img">
<div class="slide__img-inner" style="background-image: url(img/4.jpg);"></div>
</div>
</div>
<div class="slide__title">
<h3 class="slide__title-main">Caged Birds</h3>
<p class="slide__title-sub">They told me to grow roots, instead I grew wings. Birds born in a cage
think flying is an illness. </p>
</div>
</div>
<nav class="slideshow__nav slideshow__nav--arrows">
<button id="prev-slide" class="btn btn--arrow" aria-label="Previous slide"><svg class="icon icon--prev">
<use xlink:href="#icon-prev"></use>
</svg></button>
<button id="next-slide" class="btn btn--arrow" aria-label="Next slide"><svg class="icon icon--next">
<use xlink:href="#icon-next"></use>
</svg></button>
</nav>
</div>
</main>
<!-- 引入js中的三个包 -->
<script src="js/imagesloaded.pkgd.min.js"></script>
<script src="js/anime.min.js"></script>
<script src="js/main.js"></script>
<script>
(function () {
var slideshow = new MLSlideshow(document.querySelector('.slideshow'));
document.querySelector('#prev-slide').addEventListener('click', function () {
slideshow.prev();
})
document.querySelector('#next-slide').addEventListener('click', function () {
slideshow.next();
})
})()
</script>
</body>
</html>