virtual.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import $ from '../../shared/dom.js';
  2. import { setCSSProperty } from '../../shared/utils.js';
  3. export default function Virtual({
  4. swiper,
  5. extendParams,
  6. on
  7. }) {
  8. extendParams({
  9. virtual: {
  10. enabled: false,
  11. slides: [],
  12. cache: true,
  13. renderSlide: null,
  14. renderExternal: null,
  15. renderExternalUpdate: true,
  16. addSlidesBefore: 0,
  17. addSlidesAfter: 0
  18. }
  19. });
  20. let cssModeTimeout;
  21. swiper.virtual = {
  22. cache: {},
  23. from: undefined,
  24. to: undefined,
  25. slides: [],
  26. offset: 0,
  27. slidesGrid: []
  28. };
  29. function renderSlide(slide, index) {
  30. const params = swiper.params.virtual;
  31. if (params.cache && swiper.virtual.cache[index]) {
  32. return swiper.virtual.cache[index];
  33. }
  34. const $slideEl = params.renderSlide ? $(params.renderSlide.call(swiper, slide, index)) : $(`<div class="${swiper.params.slideClass}" data-swiper-slide-index="${index}">${slide}</div>`);
  35. if (!$slideEl.attr('data-swiper-slide-index')) $slideEl.attr('data-swiper-slide-index', index);
  36. if (params.cache) swiper.virtual.cache[index] = $slideEl;
  37. return $slideEl;
  38. }
  39. function update(force) {
  40. const {
  41. slidesPerView,
  42. slidesPerGroup,
  43. centeredSlides
  44. } = swiper.params;
  45. const {
  46. addSlidesBefore,
  47. addSlidesAfter
  48. } = swiper.params.virtual;
  49. const {
  50. from: previousFrom,
  51. to: previousTo,
  52. slides,
  53. slidesGrid: previousSlidesGrid,
  54. offset: previousOffset
  55. } = swiper.virtual;
  56. if (!swiper.params.cssMode) {
  57. swiper.updateActiveIndex();
  58. }
  59. const activeIndex = swiper.activeIndex || 0;
  60. let offsetProp;
  61. if (swiper.rtlTranslate) offsetProp = 'right';else offsetProp = swiper.isHorizontal() ? 'left' : 'top';
  62. let slidesAfter;
  63. let slidesBefore;
  64. if (centeredSlides) {
  65. slidesAfter = Math.floor(slidesPerView / 2) + slidesPerGroup + addSlidesAfter;
  66. slidesBefore = Math.floor(slidesPerView / 2) + slidesPerGroup + addSlidesBefore;
  67. } else {
  68. slidesAfter = slidesPerView + (slidesPerGroup - 1) + addSlidesAfter;
  69. slidesBefore = slidesPerGroup + addSlidesBefore;
  70. }
  71. const from = Math.max((activeIndex || 0) - slidesBefore, 0);
  72. const to = Math.min((activeIndex || 0) + slidesAfter, slides.length - 1);
  73. const offset = (swiper.slidesGrid[from] || 0) - (swiper.slidesGrid[0] || 0);
  74. Object.assign(swiper.virtual, {
  75. from,
  76. to,
  77. offset,
  78. slidesGrid: swiper.slidesGrid
  79. });
  80. function onRendered() {
  81. swiper.updateSlides();
  82. swiper.updateProgress();
  83. swiper.updateSlidesClasses();
  84. if (swiper.lazy && swiper.params.lazy.enabled) {
  85. swiper.lazy.load();
  86. }
  87. }
  88. if (previousFrom === from && previousTo === to && !force) {
  89. if (swiper.slidesGrid !== previousSlidesGrid && offset !== previousOffset) {
  90. swiper.slides.css(offsetProp, `${offset}px`);
  91. }
  92. swiper.updateProgress();
  93. return;
  94. }
  95. if (swiper.params.virtual.renderExternal) {
  96. swiper.params.virtual.renderExternal.call(swiper, {
  97. offset,
  98. from,
  99. to,
  100. slides: function getSlides() {
  101. const slidesToRender = [];
  102. for (let i = from; i <= to; i += 1) {
  103. slidesToRender.push(slides[i]);
  104. }
  105. return slidesToRender;
  106. }()
  107. });
  108. if (swiper.params.virtual.renderExternalUpdate) {
  109. onRendered();
  110. }
  111. return;
  112. }
  113. const prependIndexes = [];
  114. const appendIndexes = [];
  115. if (force) {
  116. swiper.$wrapperEl.find(`.${swiper.params.slideClass}`).remove();
  117. } else {
  118. for (let i = previousFrom; i <= previousTo; i += 1) {
  119. if (i < from || i > to) {
  120. swiper.$wrapperEl.find(`.${swiper.params.slideClass}[data-swiper-slide-index="${i}"]`).remove();
  121. }
  122. }
  123. }
  124. for (let i = 0; i < slides.length; i += 1) {
  125. if (i >= from && i <= to) {
  126. if (typeof previousTo === 'undefined' || force) {
  127. appendIndexes.push(i);
  128. } else {
  129. if (i > previousTo) appendIndexes.push(i);
  130. if (i < previousFrom) prependIndexes.push(i);
  131. }
  132. }
  133. }
  134. appendIndexes.forEach(index => {
  135. swiper.$wrapperEl.append(renderSlide(slides[index], index));
  136. });
  137. prependIndexes.sort((a, b) => b - a).forEach(index => {
  138. swiper.$wrapperEl.prepend(renderSlide(slides[index], index));
  139. });
  140. swiper.$wrapperEl.children('.swiper-slide').css(offsetProp, `${offset}px`);
  141. onRendered();
  142. }
  143. function appendSlide(slides) {
  144. if (typeof slides === 'object' && 'length' in slides) {
  145. for (let i = 0; i < slides.length; i += 1) {
  146. if (slides[i]) swiper.virtual.slides.push(slides[i]);
  147. }
  148. } else {
  149. swiper.virtual.slides.push(slides);
  150. }
  151. update(true);
  152. }
  153. function prependSlide(slides) {
  154. const activeIndex = swiper.activeIndex;
  155. let newActiveIndex = activeIndex + 1;
  156. let numberOfNewSlides = 1;
  157. if (Array.isArray(slides)) {
  158. for (let i = 0; i < slides.length; i += 1) {
  159. if (slides[i]) swiper.virtual.slides.unshift(slides[i]);
  160. }
  161. newActiveIndex = activeIndex + slides.length;
  162. numberOfNewSlides = slides.length;
  163. } else {
  164. swiper.virtual.slides.unshift(slides);
  165. }
  166. if (swiper.params.virtual.cache) {
  167. const cache = swiper.virtual.cache;
  168. const newCache = {};
  169. Object.keys(cache).forEach(cachedIndex => {
  170. const $cachedEl = cache[cachedIndex];
  171. const cachedElIndex = $cachedEl.attr('data-swiper-slide-index');
  172. if (cachedElIndex) {
  173. $cachedEl.attr('data-swiper-slide-index', parseInt(cachedElIndex, 10) + numberOfNewSlides);
  174. }
  175. newCache[parseInt(cachedIndex, 10) + numberOfNewSlides] = $cachedEl;
  176. });
  177. swiper.virtual.cache = newCache;
  178. }
  179. update(true);
  180. swiper.slideTo(newActiveIndex, 0);
  181. }
  182. function removeSlide(slidesIndexes) {
  183. if (typeof slidesIndexes === 'undefined' || slidesIndexes === null) return;
  184. let activeIndex = swiper.activeIndex;
  185. if (Array.isArray(slidesIndexes)) {
  186. for (let i = slidesIndexes.length - 1; i >= 0; i -= 1) {
  187. swiper.virtual.slides.splice(slidesIndexes[i], 1);
  188. if (swiper.params.virtual.cache) {
  189. delete swiper.virtual.cache[slidesIndexes[i]];
  190. }
  191. if (slidesIndexes[i] < activeIndex) activeIndex -= 1;
  192. activeIndex = Math.max(activeIndex, 0);
  193. }
  194. } else {
  195. swiper.virtual.slides.splice(slidesIndexes, 1);
  196. if (swiper.params.virtual.cache) {
  197. delete swiper.virtual.cache[slidesIndexes];
  198. }
  199. if (slidesIndexes < activeIndex) activeIndex -= 1;
  200. activeIndex = Math.max(activeIndex, 0);
  201. }
  202. update(true);
  203. swiper.slideTo(activeIndex, 0);
  204. }
  205. function removeAllSlides() {
  206. swiper.virtual.slides = [];
  207. if (swiper.params.virtual.cache) {
  208. swiper.virtual.cache = {};
  209. }
  210. update(true);
  211. swiper.slideTo(0, 0);
  212. }
  213. on('beforeInit', () => {
  214. if (!swiper.params.virtual.enabled) return;
  215. swiper.virtual.slides = swiper.params.virtual.slides;
  216. swiper.classNames.push(`${swiper.params.containerModifierClass}virtual`);
  217. swiper.params.watchSlidesProgress = true;
  218. swiper.originalParams.watchSlidesProgress = true;
  219. if (!swiper.params.initialSlide) {
  220. update();
  221. }
  222. });
  223. on('setTranslate', () => {
  224. if (!swiper.params.virtual.enabled) return;
  225. if (swiper.params.cssMode && !swiper._immediateVirtual) {
  226. clearTimeout(cssModeTimeout);
  227. cssModeTimeout = setTimeout(() => {
  228. update();
  229. }, 100);
  230. } else {
  231. update();
  232. }
  233. });
  234. on('init update resize', () => {
  235. if (!swiper.params.virtual.enabled) return;
  236. if (swiper.params.cssMode) {
  237. setCSSProperty(swiper.wrapperEl, '--swiper-virtual-size', `${swiper.virtualSize}px`);
  238. }
  239. });
  240. Object.assign(swiper.virtual, {
  241. appendSlide,
  242. prependSlide,
  243. removeSlide,
  244. removeAllSlides,
  245. update
  246. });
  247. }