최근에는 모바일 개발 시 Ele.me의 vue 프론트엔드 컴포넌트 라이브러리를 사용하고 있습니다. 단순히 컴포넌트로만 사용하고 싶지 않기 때문에 구현 원리에 대해 더 깊이 이해하고 싶습니다. 이 글은 Swiper의 모바일 효과 관련 정보를 주로 소개하고 있으며, 관심 있는 친구들이 참고하면 도움이 될 것입니다.
코드는 여기에 있습니다: poke me
1. 설명
상위 컨테이너overflow:hidden;,하위 페이지transform:translateX(-100%);width:100%;
2. 핵심 분석
2.1 페이지 초기화
모든 페이지가 모바일 화면 왼쪽에 한 화면 너비로 위치하기 때문에 초기 상황은 해당 페이지에 하위 페이지가 보이지 않는 상태이므로 첫 번째 단계는 표시되어야 하는 하위 페이지를 설정해야 합니다. 기본적으로 defaultIndex: 0
function reInitPages() { // 得出页面是否能够被滑动 // 1. 子页面只有一个 // 2. 用户手动设置不能滑动 noDragWhenSingle = true noDrag = children.length === 1 && noDragWhenSingle; var aPages = []; var intDefaultIndex = Math.floor(defaultIndex); var defaultIndex = (intDefaultIndex >= 0 && intDefaultIndex < children.length) ? intDefaultIndex : 0; // 得到当前被激活的子页面索引 index = defaultIndex; children.forEach(function(child, index) { aPages.push(child); // 所有页面移除激活class child.classList.remove('is-active'); if (index === defaultIndex) { // 给激活的子页面加上激活class child.classList.add('is-active'); } }); pages = aPages; }
2.2 컨테이너 슬라이딩 시작(onTouchStart)
하위 버전의 android 휴대폰에서는 event.preventDefault(를 설정합니다. ) 시작됩니다. 특정 성능 개선 효과가 있어 슬라이딩이 덜 걸리게 됩니다.
사전 작업:
사용자가 Prevent:true로 설정한 경우 슬라이딩 시 기본 동작을 방지합니다.
사용자가 stopPropagation:true로 설정한 경우 슬라이딩 시 이벤트가 위로 전파되는 것을 방지합니다.
애니메이션이 아직 끝나지 않았습니다. 슬라이딩 방지
드래그 설정: true, 슬라이딩 시작
사용자 스크롤을 false로 설정
드래그 시작:
전역 개체를 사용하여 정보를 기록합니다.
dragState = { startTime // 开始时间 startLeft // 开始的X坐标 startTop // 开始的Y坐标(相对于整个页面viewport pageY) startTopAbsolute // 绝对Y坐标(相对于文档顶部 clientY) pageWidth // 一个页面宽度 pageHeight // 一个页面的高度 prevPage // 上一个页面 dragPage // 当前页面 nextPage // 下一个页面 };
2.3 컨테이너 슬라이드(onTouchMove)
전역 dragState를 적용하고 새 정보를 기록합니다
dragState = { currentLeft // 开始的X坐标 currentTop // 开始的Y坐标(相对于整个页面viewport pageY) currentTopAbsolute // 绝对Y坐标(相对于文档顶部 clientY) };
그런 다음 시작 및 슬라이드의 정보를 통해 무언가를 계산할 수 있습니다.
수평 변위 슬라이드 (offsetLeft = currentLeft - startLeft)
슬라이드의 수직 변위 (offsetTop = currentTopAbsolute - startTopAbsolute)
사용자의 자연스러운 스크롤인가요? 여기서 자연스러운 스크롤은 사용자가 스와이프를 슬라이드하고 싶지 않다는 뜻이지만, 페이지를 슬라이드하고 싶습니다
// 条件 // distanceX = Math.abs(offsetLeft); // distanceY = Math.abs(offsetTop); distanceX < 5 || ( distanceY >= 5 && distanceY >= 1.73 * distanceX )
왼쪽 또는 오른쪽으로 이동할지 결정합니다(offsetLeft
변위 재설정
// 如果存在上一个页面并且是左移 if (dragState.prevPage && towards === 'prev') { // 重置上一个页面的水平位移为 offsetLeft - dragState.pageWidth // 由于 offsetLeft 一直在变化,并且 >0 // 那么也就是说 offsetLeft - dragState.pageWidth 的值一直在变大,但是仍未负数 // 这就是为什么当连续属性存在的时候左滑会看到上一个页面会跟着滑动的原因 // 这里的 translate 方法其实很简单,在滑动的时候去除了动画效果`transition`,单纯改变位移 // 而在滑动结束的时候,加上`transition`,使得滑动到最后释放的过渡更加自然 translate(dragState.prevPage, offsetLeft - dragState.pageWidth); } // 当前页面跟着滑动 translate(dragState.dragPage, offsetLeft); // 后一个页面同理 if (dragState.nextPage && towards === 'next') { translate(dragState.nextPage, offsetLeft + dragState.pageWidth); }
2.4 슬라이드 끝(onTouchEnd)
Pre -work:
슬라이딩하는 동안 사용자의 자연스러운 스크롤인지 실시간으로 판단할 수 있습니다. be done:
dragging = false; dragState = {};
를 실행하여 탭 이벤트인지 확인합니다
// 时间小于300ms,click事件延迟300ms触发 // 水平位移和垂直位移栋小于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 或者 滑动位移超过屏幕宽度 1/2, 根据位移判断方向 if (dragDuration < 300 || Math.abs(offsetLeft) > pageWidth / 2) { towards = offsetLeft < 0 ? 'next' : 'prev'; } // 如果非连续,当处于第一页,不会出现上一页,当处于最后一页,不会出现下一页 if (!continuous) { if ((index === 0 && towards === 'prev') || (index === pageCount - 1 && towards === 'next')) { towards = null; } } // 子页面数量小于2时,不执行滑动动画 if (children.length < 2) { towards = null; }
애니메이션 실행
// 当没有options的时候,为自然滑动,也就是定时器滑动 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; // 定时器滑动 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]; } } // 计算上一页与下一页之后 // 重置位移 // 参看doOnTouchMove // 其实这里的options 传与不传也就是获取上一页信息与下一页信息 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]; // 得到滑动之后的新的索引 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; } } // 动画完成之后的回调 var callback = function() { // 得到滑动之后的激活页面,添加激活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() { // 向后滑动 if (towards === 'next') { isDone = true; before(currentPage); // 当前页执行动画,完成后执行callback translate(currentPage, -pageWidth, speed, callback); if (nextPage) { // 下一面移动视野中 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; // 当前页面依旧处于视野中 // 上一页和下一页滑出 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); }
에 저장된 상태 정보 지우기 슬라이딩 사이클
dragging = false; dragState = {};
전체적으로 구현 원리는 비교적 간단합니다. 슬라이딩은 초기 위치를 기록하기 시작하고 이전 페이지와 다음 페이지에 표시되어야 하는 페이지를 계산합니다. 슬라이딩이 끝나면 이전 페이지와 다음 페이지의 이동 결과에 따라 해당 애니메이션이 실행됩니다.
한 가지 세부 사항은 슬라이딩 중 전환으로 인해 이전 페이지와 다음 페이지가 부자연스럽게 이동하는 것을 방지하기 위해
전환효과가 비어 있도록 설정된다는 것입니다. 애니메이션 효과가 끝난 후에 추가하세요. 관련 추천:
위 내용은 모바일에서 Swiper를 사용하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!