寫在前面
成都創(chuàng)新互聯(lián)自2013年創(chuàng)立以來(lái),先為汾西等服務(wù)建站,汾西等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為汾西企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。
最近在做移動(dòng)端方面運(yùn)用到了餓了么的vue前端組件庫(kù),因?yàn)椴幌雴渭冇媒M件而使用它,故想深入了解一下實(shí)現(xiàn)原理。后續(xù)將會(huì)繼續(xù)研究一下其他的組件實(shí)現(xiàn)原理,有興趣的可以關(guān)注下。

代碼在這里:戳我
1. 說(shuō)明
父容器overflow:hidden;,子頁(yè)面transform:translateX(-100%);width:100%;
2. 核心解析
2.1 頁(yè)面初始化
由于所有頁(yè)面都在手機(jī)屏幕左側(cè)一個(gè)屏幕寬度的位置,因此最開(kāi)始的情況是頁(yè)面中看不到任何一個(gè)子頁(yè)面,所以第一步應(yīng)該設(shè)置應(yīng)該顯示的子頁(yè)面,默認(rèn)情況下defaultIndex:0
function reInitPages() {
// 得出頁(yè)面是否能夠被滑動(dòng)
// 1. 子頁(yè)面只有一個(gè)
// 2. 用戶手動(dòng)設(shè)置不能滑動(dòng) noDragWhenSingle = true
noDrag = children.length === 1 && noDragWhenSingle;
var aPages = [];
var intDefaultIndex = Math.floor(defaultIndex);
var defaultIndex = (intDefaultIndex >= 0 && intDefaultIndex < children.length)
? intDefaultIndex : 0;
// 得到當(dāng)前被激活的子頁(yè)面索引
index = defaultIndex;
children.forEach(function(child, index) {
aPages.push(child);
// 所有頁(yè)面移除激活class
child.classList.remove('is-active');
if (index === defaultIndex) {
// 給激活的子頁(yè)面加上激活class
child.classList.add('is-active');
}
});
pages = aPages;
}
2.2 容器滑動(dòng)開(kāi)始(onTouchStart)
在低版本的android手機(jī)上,設(shè)置event.preventDefault()會(huì)起到一定的性能提升作用,使得滑動(dòng)起來(lái)不是那么卡。
前置工作:
滑動(dòng)開(kāi)始:
使用一個(gè)全局對(duì)象記錄信息,這些信息包括:
dragState = {
startTime // 開(kāi)始時(shí)間
startLeft // 開(kāi)始的X坐標(biāo)
startTop // 開(kāi)始的Y坐標(biāo)(相對(duì)于整個(gè)頁(yè)面viewport pageY)
startTopAbsolute // 絕對(duì)Y坐標(biāo)(相對(duì)于文檔頂部 clientY)
pageWidth // 一個(gè)頁(yè)面寬度
pageHeight // 一個(gè)頁(yè)面的高度
prevPage // 上一個(gè)頁(yè)面
dragPage // 當(dāng)前頁(yè)面
nextPage // 下一個(gè)頁(yè)面
};
2.3 容器滑動(dòng)(onTouchMove)
套用全局dragState,記錄新的信息
dragState = {
currentLeft // 開(kāi)始的X坐標(biāo)
currentTop // 開(kāi)始的Y坐標(biāo)(相對(duì)于整個(gè)頁(yè)面viewport pageY)
currentTopAbsolute // 絕對(duì)Y坐標(biāo)(相對(duì)于文檔頂部 clientY)
};那么我們就可以通過(guò)開(kāi)始和滑動(dòng)中的信息來(lái)計(jì)算出一些東西:
滑動(dòng)的水平位移(offsetLeft = currentLeft - startLeft)
滑動(dòng)的垂直位移(offsetTop = currentTopAbsolute - startTopAbsolute)
是否是用戶的自然滾動(dòng),這里的自然滾動(dòng)說(shuō)的是用戶并不是想滑動(dòng)swiper,而是想滑動(dòng)頁(yè)面
// 條件 // distanceX = Math.abs(offsetLeft); // distanceY = Math.abs(offsetTop); distanceX < 5 || ( distanceY >= 5 && distanceY >= 1.73 * distanceX )
判斷是左移還是右移(offsetLeft < 0 左移,反之,右移)
重置位移
// 如果存在上一個(gè)頁(yè)面并且是左移
if (dragState.prevPage && towards === 'prev') {
// 重置上一個(gè)頁(yè)面的水平位移為 offsetLeft - dragState.pageWidth
// 由于 offsetLeft 一直在變化,并且 >0
// 那么也就是說(shuō) offsetLeft - dragState.pageWidth 的值一直在變大,但是仍未負(fù)數(shù)
// 這就是為什么當(dāng)連續(xù)屬性存在的時(shí)候左滑會(huì)看到上一個(gè)頁(yè)面會(huì)跟著滑動(dòng)的原因
// 這里的 translate 方法其實(shí)很簡(jiǎn)單,在滑動(dòng)的時(shí)候去除了動(dòng)畫效果`transition`,單純改變位移
// 而在滑動(dòng)結(jié)束的時(shí)候,加上`transition`,使得滑動(dòng)到最后釋放的過(guò)渡更加自然
translate(dragState.prevPage, offsetLeft - dragState.pageWidth);
}
// 當(dāng)前頁(yè)面跟著滑動(dòng)
translate(dragState.dragPage, offsetLeft);
// 后一個(gè)頁(yè)面同理
if (dragState.nextPage && towards === 'next') {
translate(dragState.nextPage, offsetLeft + dragState.pageWidth);
}
2.4 滑動(dòng)結(jié)束(onTouchEnd)
前置工作:
在滑動(dòng)中,我們是可以實(shí)時(shí)地來(lái)判斷到底是不是用戶的自然滾動(dòng)userScrolling,如果是用戶自然滾動(dòng),那么swiper的滑動(dòng)信息就不算數(shù),因此要做一些清除操作:
dragging = false;
dragState = {};
當(dāng)然如果userScrolling:false,那么就是滑動(dòng)子頁(yè)面,執(zhí)行doOnTouchEnd方法
判斷是否是tap事件
// 時(shí)間小于300ms,click事件延遲300ms觸發(fā)
// 水平位移和垂直位移棟小于5像素
if (dragDuration < 300) {
var fireTap = Math.abs(offsetLeft) < 5 && Math.abs(offsetTop < 5);
if (isNaN(offsetLeft) || isNaN(offsetTop)) {
fireTap = true;
}
if (fireTap) {
console.log('tap');
}
}
判斷方向
// 如果事件間隔小于300ms但是滑出屏幕,直接返回
if (dragDuration < 300 && dragState.currentLeft === undefined) return;
// 如果事件間隔小于300ms 或者 滑動(dòng)位移超過(guò)屏幕寬度 1/2, 根據(jù)位移判斷方向
if (dragDuration < 300 || Math.abs(offsetLeft) > pageWidth / 2) {
towards = offsetLeft < 0 ? 'next' : 'prev';
}
// 如果非連續(xù),當(dāng)處于第一頁(yè),不會(huì)出現(xiàn)上一頁(yè),當(dāng)處于最后一頁(yè),不會(huì)出現(xiàn)下一頁(yè)
if (!continuous) {
if ((index === 0 && towards === 'prev')
|| (index === pageCount - 1 && towards === 'next')) {
towards = null;
}
}
// 子頁(yè)面數(shù)量小于2時(shí),不執(zhí)行滑動(dòng)動(dòng)畫
if (children.length < 2) {
towards = null;
}
執(zhí)行動(dòng)畫
// 當(dāng)沒(méi)有options的時(shí)候,為自然滑動(dòng),也就是定時(shí)器滑動(dòng)
function doAnimate(towards, options) {
if (children.length === 0) return;
if (!options && children.length < 2) return;
var prevPage, nextPage, currentPage, pageWidth, offsetLeft;
var pageCount = pages.length;
// 定時(shí)器滑動(dòng)
if (!options) {
pageWidth = element.clientWidth;
currentPage = pages[index];
prevPage = pages[index - 1];
nextPage = pages[index + 1];
if (continuous && pages.length > 1) {
if (!prevPage) {
prevPage = pages[pages.length - 1];
}
if (!nextPage) {
nextPage = pages[0];
}
}
// 計(jì)算上一頁(yè)與下一頁(yè)之后
// 重置位移
// 參看doOnTouchMove
// 其實(shí)這里的options 傳與不傳也就是獲取上一頁(yè)信息與下一頁(yè)信息
if (prevPage) {
prevPage.style.display = 'block';
translate(prevPage, -pageWidth);
}
if (nextPage) {
nextPage.style.display = 'block';
translate(nextPage, pageWidth);
}
} else {
prevPage = options.prevPage;
currentPage = options.currentPage;
nextPage = options.nextPage;
pageWidth = options.pageWidth;
offsetLeft = options.offsetLeft;
}
var newIndex;
var oldPage = children[index];
// 得到滑動(dòng)之后的新的索引
if (towards === 'prev') {
if (index > 0) {
newIndex = index - 1;
}
if (continuous && index === 0) {
newIndex = pageCount - 1;
}
} else if (towards === 'next') {
if (index < pageCount - 1) {
newIndex = index + 1;
}
if (continuous && index === pageCount - 1) {
newIndex = 0;
}
}
// 動(dòng)畫完成之后的回調(diào)
var callback = function() {
// 得到滑動(dòng)之后的激活頁(yè)面,添加激活class
// 重新賦值索引
if (newIndex !== undefined) {
var newPage = children[newIndex];
oldPage.classList.remove('is-active');
newPage.classList.add('is-active');
index = newIndex
}
if (isDone) {
end();
}
if (prevPage) {
prevPage.style.display = '';
}
if (nextPage) {
nextPage.style.display = '';
}
}
setTimeout(function() {
// 向后滑動(dòng)
if (towards === 'next') {
isDone = true;
before(currentPage);
// 當(dāng)前頁(yè)執(zhí)行動(dòng)畫,完成后執(zhí)行callback
translate(currentPage, -pageWidth, speed, callback);
if (nextPage) {
// 下一面移動(dòng)視野中
translate(nextPage, 0, speed)
}
} else if (towards === 'prev') {
isDone = true;
before(currentPage);
translate(currentPage, pageWidth, speed, callback);
if (prevPage) {
translate(prevPage, 0, speed);
}
} else {
// 如果既不是左滑也不是右滑
isDone = true;
// 當(dāng)前頁(yè)面依舊處于視野中
// 上一頁(yè)和下一頁(yè)滑出
translate(currentPage, 0, speed, callback);
if (typeof offsetLeft !== 'undefined') {
if (prevPage && offsetLeft > 0) {
translate(prevPage, pageWidth * -1, speed);
}
if (nextPage && offsetLeft < 0) {
translate(nextPage, pageWidth, speed);
}
} else {
if (prevPage) {
translate(prevPage, pageWidth * -1, speed);
}
if (nextPage) {
translate(nextPage, pageWidth, speed);
}
}
}
}, 10);
}
后置工作:
清除一次滑動(dòng)周期中保存的狀態(tài)信息
dragging = false;
dragState = {};
總結(jié)
整體來(lái)說(shuō)實(shí)現(xiàn)原理還是比較簡(jiǎn)單的,滑動(dòng)開(kāi)始記錄初始位置,計(jì)算上一頁(yè)與下一頁(yè)的應(yīng)該展示的頁(yè)面;滑動(dòng)中計(jì)算位移,計(jì)算上一頁(yè)下一頁(yè)的位移;滑動(dòng)結(jié)束根據(jù)位移結(jié)果執(zhí)行相應(yīng)的動(dòng)畫。
有一個(gè)細(xì)節(jié)就是,在滑動(dòng)中transition的效果置為空,是為了防止在滑動(dòng)中上一頁(yè)與下一頁(yè)因?yàn)檫^(guò)渡存在而位移得不自然,在滑動(dòng)結(jié)束后再給他們加上動(dòng)畫效果。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
文章標(biāo)題:移動(dòng)端效果之Swiper詳解
網(wǎng)頁(yè)地址:http://chinadenli.net/article38/isgisp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、云服務(wù)器、網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)、網(wǎng)站維護(hù)、域名注冊(cè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)