前期资料参考:
注: 将 github.com
改为 github1s.com
有奇效!
轮播图
github地址: Carousel
核心实现
检查图片是否加载完全
| / 每隔一段时间检查图片是否加载完全 let checkInterval = 50; let checkTimer = setInterval(() => { if (this.isImagesComplete) { clearInterval(checkTimer); this.initCarousel(); this.initDots(); this.initArrows(); } }, checkInterval)
|
| isImagesComplete() { let complete = 0; for (let i = 0; i < this.carouselWrap.children.length; i++) { if (this.carouselWrap.children[i].complete) { complete++; } } return complete === this.carouselWrap.children.length; }
|
轮播列表添加首尾过渡元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| getCarouselWrap() { let fragment = document.createDocumentFragment(); let imgTemplate = document.createElement('img'); this.options.carouselWrapImages.forEach((imgUrl, idx) => { let imgElement = imgTemplate.cloneNode(false); imgElement.setAttribute('class', Carousel.CLASS.CAROUSEL_IMAGE); imgElement.setAttribute('src', imgUrl); imgElement.setAttribute('alt', idx + 1); fragment.appendChild(imgElement) }) let first = fragment.firstElementChild.cloneNode(true); let last = fragment.lastElementChild.cloneNode(true); fragment.insertBefore(last, fragment.firstElementChild); fragment.appendChild(first);
let carouselWrap = document.createElement('div'); carouselWrap.setAttribute('class', Carousel.CLASS.CAROUSEL_WRAP); carouselWrap.setAttribute('id', Carousel.ID.CAROUSEL_WRAP.substring(1));
carouselWrap.appendChild(fragment); this.setWidth(carouselWrap, carouselWrap.children.length * this.options.carouselWidth);
return carouselWrap; }
|
动画效果实现
主要依靠 style.left
和 requestAnimationFrame()
实现; 此外在完成动画后需要处理过渡元素;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| switchAnimation(targetLeft, offset) { this.isAnimationShow = true; let currentLeft = this.getLeft(this.carouselWrap); this.animationTimer = requestAnimationFrame(() => { if ((offset < 0 && currentLeft > targetLeft) || (offset > 0 && currentLeft < targetLeft)) { this.frameAnimation(targetLeft, offset) } else { clearInterval(this.animationTimer); this.resetCarouselWrap(targetLeft, offset) } }) }
frameAnimation(targetLeft, offset) { this.setLeft(this.carouselWrap, this.getLeft(this.carouselWrap) + offset); this.switchAnimation(targetLeft, offset); }
resetCarouselWrap(targetLeft, offset) { if (this.isDotClick) { this.resetDotCarouselWrap(offset) } else { this.resetMoveCarouselWrap(targetLeft); } this.isDotClick = false; this.isAnimationShow = false; }
resetDotCarouselWrap(offset) { this.setLeft(this.carouselWrap, -this.options.carouselWidth * this.dotIndex) if (offset < 0) { this.carouselWrap.removeChild(this.currentNode.nextElementSibling); } if (offset > 0) { this.carouselWrap.removeChild(this.currentNode.previousElementSibling); } }
resetMoveCarouselWrap(targetLeft) { if (targetLeft < -this.carouselCount * this.options.carouselWidth) { this.setLeft(this.carouselWrap, -this.options.carouselWidth); } if (targetLeft > -this.options.carouselWidth) { this.setLeft(this.carouselWrap, -this.carouselCount * this.options.carouselWidth); } }
|
通过轮播圆点执行的动画
在轮播片段一侧插入目标副本, 实现动画后再重置到真正的目标, 并删除副本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| dotChangeCarousel() { this.currentNode = this.carouselWrap.children[this.carouselIndex]; this.targetNode = this.carouselWrap.children[this.dotIndex]; if (this.dotIndex > this.carouselIndex) { this.carouselWrap.insertBefore(this.targetNode.cloneNode(true), this.currentNode.nextElementSibling); this.switchAnimation( this.getLeft(this.carouselWrap) - this.options.carouselWidth, -this.animationOffset ) } if (this.dotIndex < this.carouselIndex) { this.carouselWrap.insertBefore(this.targetNode.cloneNode(true), this.currentNode); this.setLeft(this.carouselWrap, -this.options.carouselWidth * (this.carouselIndex + 1)); this.switchAnimation( this.getLeft(this.carouselWrap) + this.options.carouselWidth, this.animationOffset ) } }
|
存在的问题
- 频繁的 DOM 操作, 性能低
- 动画算法不利于维护与功能拓展
解决方案
实现轮播的动画效果, 不需要去计算每个子元素的序号以及对应的顺序变化, 因为不管哪个方向, 自始至终我们看到的都是两个子元素的移动效果, 所以只需要给这两个移动中的元素添加对应的样式即可;
具体实现思路: 使用 CSS3 的 transform 属性来控制子元素的位置变化, 配合transition添加过渡动画, 在 JS 代码中只需要在合适的时机添加对应的类名, 然后移出对应的类名;
知识点总结
出现的页码对应”JavaScript 高级程序设计(第四版)”
- 跨浏览器事件处理程序: “红宝书第四版 P498”
- node.appendChild(): “红宝书第四版 P405-406”
- document.querySelector(CSSDescription): “红宝书第四版 P445-446”
- HTMLElement.style 属性及其设置方式(3种): 1. elem.style.width = value + ‘px’; 2. elem.setAttribute(‘style’, value + ‘px’); 3. elem.style.cssText = `width: ${value}px`;
- parseInt(): “红宝书第四版 P37”
- DocumentFragment 类型: “红宝书第四版 P424-425”
- Element 类型 (元素创建及属性设置): “红宝书第四版 P417-419”
- Element Traversal API: “红宝书第四版 P447”
- children 属性: “红宝书第四版 P456-457”
- 检测标签页是否活跃
- 定时器 setInterval: “红宝书第四版 P368-369”
- requesetAnimationFrame() “红宝书第四版 P550-551”
- element.innerHTML 属性: P452-453
- 转义字符
'<'
与 '>'
: 表示 ‘<’ 和 ‘>’
- 浏览器回流: https://segmentfault.com/a/1190000017491520