Source: src/Flicking.ts

  1. /**
  2. * Copyright (c) 2015 NAVER Corp.
  3. * egjs projects are licensed under the MIT license
  4. */
  5. import Component from "@egjs/component";
  6. import ImReady from "@egjs/imready";
  7. import Viewport from "./components/Viewport";
  8. import Panel from "./components/Panel";
  9. import { merge, getProgress, parseElement, isString, counter, findIndex } from "./utils";
  10. import { DEFAULT_OPTIONS, EVENTS, DIRECTION, AXES_EVENTS, STATE_TYPE, DEFAULT_MOVE_TYPE_OPTIONS } from "./consts";
  11. import {
  12. FlickingOptions,
  13. FlickingEvent,
  14. Direction,
  15. EventType,
  16. FlickingPanel,
  17. TriggerCallback,
  18. FlickingContext,
  19. FlickingStatus,
  20. Plugin,
  21. ElementLike,
  22. DestroyOption,
  23. BeforeSyncResult,
  24. SyncResult,
  25. ChangeEvent,
  26. SelectEvent,
  27. NeedPanelEvent,
  28. VisibleChangeEvent,
  29. ContentErrorEvent,
  30. MoveTypeStringOption,
  31. ValueOf,
  32. } from "./types";
  33. // import { sendEvent } from "./ga/ga";
  34. import { DiffResult } from "@egjs/list-differ";
  35. /**
  36. * @memberof eg
  37. * @extends eg.Component
  38. * @support {"ie": "10+", "ch" : "latest", "ff" : "latest", "sf" : "latest" , "edge" : "latest", "ios" : "7+", "an" : "4.X+"}
  39. * @requires {@link https://github.com/naver/egjs-component|eg.Component}
  40. * @requires {@link https://github.com/naver/egjs-axes|eg.Axes}
  41. * @see Easing Functions Cheat Sheet {@link http://easings.net/} <ko>이징 함수 Cheat Sheet {@link http://easings.net/}</ko>
  42. */
  43. class Flicking extends Component<{
  44. holdStart: FlickingEvent;
  45. holdEnd: FlickingEvent;
  46. moveStart: FlickingEvent;
  47. move: FlickingEvent;
  48. moveEnd: FlickingEvent;
  49. change: ChangeEvent;
  50. restore: FlickingEvent;
  51. select: SelectEvent;
  52. needPanel: NeedPanelEvent;
  53. visibleChange: VisibleChangeEvent;
  54. contentError: ContentErrorEvent;
  55. }> {
  56. /**
  57. * Version info string
  58. * @ko 버전정보 문자열
  59. * @example
  60. * eg.Flicking.VERSION; // ex) 3.0.0
  61. * @memberof eg.Flicking
  62. */
  63. public static VERSION: string = "#__VERSION__#";
  64. /**
  65. * Direction constant - "PREV" or "NEXT"
  66. * @ko 방향 상수 - "PREV" 또는 "NEXT"
  67. * @type {object}
  68. * @property {"PREV"} PREV - Prev direction from current hanger position.<br/>It's `left(←️)` direction when `horizontal: true`.<br/>Or, `up(↑️)` direction when `horizontal: false`.<ko>현재 행어를 기준으로 이전 방향.<br/>`horizontal: true`일 경우 `왼쪽(←️)` 방향.<br/>`horizontal: false`일 경우 `위쪽(↑️)`방향이다.</ko>
  69. * @property {"NEXT"} NEXT - Next direction from current hanger position.<br/>It's `right(→)` direction when `horizontal: true`.<br/>Or, `down(↓️)` direction when `horizontal: false`.<ko>현재 행어를 기준으로 다음 방향.<br/>`horizontal: true`일 경우 `오른쪽(→)` 방향.<br/>`horizontal: false`일 경우 `아래쪽(↓️)`방향이다.</ko>
  70. * @example
  71. * eg.Flicking.DIRECTION.PREV; // "PREV"
  72. * eg.Flicking.DIRECTION.NEXT; // "NEXT"
  73. */
  74. public static DIRECTION: Direction = DIRECTION;
  75. /**
  76. * Event type object with event name strings.
  77. * @ko 이벤트 이름 문자열들을 담은 객체
  78. * @type {object}
  79. * @property {"holdStart"} HOLD_START - holdStart event<ko>holdStart 이벤트</ko>
  80. * @property {"holdEnd"} HOLD_END - holdEnd event<ko>holdEnd 이벤트</ko>
  81. * @property {"moveStart"} MOVE_START - moveStart event<ko>moveStart 이벤트</ko>
  82. * @property {"move"} MOVE - move event<ko>move 이벤트</ko>
  83. * @property {"moveEnd"} MOVE_END - moveEnd event<ko>moveEnd 이벤트</ko>
  84. * @property {"change"} CHANGE - change event<ko>change 이벤트</ko>
  85. * @property {"restore"} RESTORE - restore event<ko>restore 이벤트</ko>
  86. * @property {"select"} SELECT - select event<ko>select 이벤트</ko>
  87. * @property {"needPanel"} NEED_PANEL - needPanel event<ko>needPanel 이벤트</ko>
  88. * @example
  89. * eg.Flicking.EVENTS.MOVE_START; // "MOVE_START"
  90. */
  91. public static EVENTS: EventType = EVENTS;
  92. public options: FlickingOptions;
  93. private wrapper: HTMLElement;
  94. private viewport: Viewport;
  95. private contentsReadyChecker: ImReady | null = null;
  96. private eventContext: FlickingContext;
  97. private isPanelChangedAtBeforeSync: boolean = false;
  98. /**
  99. * @param element A base element for the eg.Flicking module. When specifying a value as a `string` type, you must specify a css selector string to select the element.<ko>eg.Flicking 모듈을 사용할 기준 요소. `string`타입으로 값 지정시 요소를 선택하기 위한 css 선택자 문자열을 지정해야 한다.</ko>
  100. * @param options An option object of the eg.Flicking module<ko>eg.Flicking 모듈의 옵션 객체</ko>
  101. * @param {string} [options.classPrefix="eg-flick"] A prefix of class names will be added for the panels, viewport, and camera.<ko>패널들과 뷰포트, 카메라에 추가될 클래스 이름의 접두사.</ko>
  102. * @param {number} [options.deceleration=0.0075] Deceleration value for panel movement animation for animation triggered by manual user input. A higher value means a shorter running time.<ko>사용자의 동작으로 가속도가 적용된 패널 이동 애니메이션의 감속도. 값이 높을수록 애니메이션 실행 시간이 짧아진다.</ko>
  103. * @param {boolean} [options.horizontal=true] The direction of panel movement. (true: horizontal, false: vertical)<ko>패널 이동 방향. (true: 가로방향, false: 세로방향)</ko>
  104. * @param {boolean} [options.circular=false] Enables circular mode, which connects first/last panel for continuous scrolling.<ko>순환 모드를 활성화한다. 순환 모드에서는 양 끝의 패널이 서로 연결되어 끊김없는 스크롤이 가능하다.</ko>
  105. * @param {boolean} [options.infinite=false] Enables infinite mode, which can automatically trigger needPanel until reaching the last panel's index reaches the lastIndex.<ko>무한 모드를 활성화한다. 무한 모드에서는 needPanel 이벤트를 자동으로 트리거한다. 해당 동작은 마지막 패널의 인덱스가 lastIndex와 일치할때까지 일어난다.</ko>
  106. * @param {number} [options.infiniteThreshold=0] A Threshold from viewport edge before triggering `needPanel` event in infinite mode.<ko>무한 모드에서 `needPanel`이벤트가 발생하기 위한 뷰포트 끝으로부터의 최대 거리.</ko>
  107. * @param {number} [options.lastIndex=Infinity] Maximum panel index that Flicking can set. Flicking won't trigger `needPanel` when the event's panel index is greater than it.<br/>Also, if the last panel's index reached a given index, you can't add more panels.<ko>Flicking이 설정 가능한 패널의 최대 인덱스. `needPanel` 이벤트에 지정된 인덱스가 최대 패널의 개수보다 같거나 커야 하는 경우에 이벤트를 트리거하지 않게 한다.<br>또한, 마지막 패널의 인덱스가 주어진 인덱스와 동일할 경우, 새로운 패널을 더 이상 추가할 수 없다.</ko>
  108. * @param {number} [options.threshold=40] Movement threshold to change panel(unit: pixel). It should be dragged above the threshold to change the current panel.<ko>패널 변경을 위한 이동 임계값 (단위: 픽셀). 주어진 값 이상으로 스크롤해야만 패널 변경이 가능하다.</ko>
  109. * @param {number} [options.duration=100] Duration of the panel movement animation. (unit: ms)<ko>패널 이동 애니메이션 진행 시간.(단위: ms)</ko>
  110. * @param {function} [options.panelEffect=x => 1 - Math.pow(1 - x, 3)] An easing function applied to the panel movement animation. Default value is `easeOutCubic`.<ko>패널 이동 애니메이션에 적용할 easing함수. 기본값은 `easeOutCubic`이다.</ko>
  111. * @param {number} [options.defaultIndex=0] Index of the panel to set as default when initializing. A zero-based integer.<ko>초기화시 지정할 디폴트 패널의 인덱스로, 0부터 시작하는 정수.</ko>
  112. * @param {string[]} [options.inputType=["touch,"mouse"]] Types of input devices to enable.({@link https://naver.github.io/egjs-axes/release/latest/doc/global.html#PanInputOption Reference})<ko>활성화할 입력 장치 종류. ({@link https://naver.github.io/egjs-axes/release/latest/doc/global.html#PanInputOption 참고})</ko>
  113. * @param {number} [options.thresholdAngle=45] The threshold angle value(0 ~ 90).<br>If the input angle from click/touched position is above or below this value in horizontal and vertical mode each, scrolling won't happen.<ko>스크롤 동작을 막기 위한 임계각(0 ~ 90).<br>클릭/터치한 지점으로부터 계산된 사용자 입력의 각도가 horizontal/vertical 모드에서 각각 크거나 작으면, 스크롤 동작이 이루어지지 않는다.</ko>
  114. * @param {number|string|number[]|string[]} [options.bounce=[10,10]] The size value of the bounce area. Only can be enabled when `circular=false`.<br>You can set different bounce value for prev/next direction by using array.<br>`number` for px value, and `string` for px, and % value relative to viewport size.(ex - 0, "10px", "20%")<ko>바운스 영역의 크기값. `circular=false`인 경우에만 사용할 수 있다.<br>배열을 통해 prev/next 방향에 대해 서로 다른 바운스 값을 지정 가능하다.<br>`number`를 통해 px값을, `stirng`을 통해 px 혹은 뷰포트 크기 대비 %값을 사용할 수 있다.(ex - 0, "10px", "20%")</ko>
  115. * @param {boolean} [options.autoResize=false] Whether the `resize` method should be called automatically after a window resize event.<ko>window의 `resize` 이벤트 이후 자동으로 resize()메소드를 호출할지의 여부.</ko>
  116. * @param {boolean} [options.adaptive=false] Whether the height(horizontal)/width(vertical) of the viewport element reflects the height/width value of the panel after completing the movement.<ko>목적 패널로 이동한 후 그 패널의 높이(horizontal)/너비(vertical)값을 뷰포트 요소의 높이/너비값에 반영할지 여부.</ko>
  117. * @param {number|""} [options.zIndex=2000] z-index value for viewport element.<ko>뷰포트 엘리먼트의 z-index 값.</ko>
  118. * @param {boolean} [options.bound=false] Prevent the view from going out of the first/last panel. Only can be enabled when `circular=false`.<ko>뷰가 첫번째와 마지막 패널 밖으로 나가는 것을 막아준다. `circular=false`인 경우에만 사용할 수 있다.</ko>
  119. * @param {boolean} [options.overflow=false] Disables CSS property `overflow: hidden` in viewport if `true`.<ko>`true`로 설정시 뷰포트에 `overflow: hidden` 속성을 해제한다.</ko>
  120. * @param {string} [options.hanger="50%"] The reference position of the hanger in the viewport, which hangs panel anchors should be stopped at.<br>It should be provided in px or % value of viewport size.<br>You can combinate those values with plus/minus sign.<br>ex) "50", "100px", "0%", "25% + 100px"<ko>뷰포트 내부의 행어의 위치. 패널의 앵커들이 뷰포트 내에서 멈추는 지점에 해당한다.<br>px값이나, 뷰포트의 크기 대비 %값을 사용할 수 있고, 이를 + 혹은 - 기호로 연계하여 사용할 수도 있다.<br>예) "50", "100px", "0%", "25% + 100px"</ko>
  121. * @param {string} [options.anchor="50%"] The reference position of the anchor in panels, which can be hanged by viewport hanger.<br>It should be provided in px or % value of panel size.<br>You can combinate those values with plus/minus sign.<br>ex) "50", "100px", "0%", "25% + 100px"<ko>패널 내부의 앵커의 위치. 뷰포트의 행어와 연계하여 패널이 화면 내에서 멈추는 지점을 설정할 수 있다.<br>px값이나, 패널의 크기 대비 %값을 사용할 수 있고, 이를 + 혹은 - 기호로 연계하여 사용할 수도 있다.<br>예) "50", "100px", "0%", "25% + 100px"</ko>
  122. * @param {number} [options.gap=0] Space value between panels. Should be given in number.(px)<ko>패널간에 부여할 간격의 크기를 나타내는 숫자.(px)</ko>
  123. * @param {eg.Flicking.MoveTypeOption} [options.moveType="snap"] Movement style by user input. (ex: snap, freeScroll)<ko>사용자 입력에 의한 이동 방식.(ex: snap, freeScroll)</ko>
  124. * @param {boolean} [options.useOffset=false] Whether to use `offsetWidth`/`offsetHeight` instead of `getBoundingClientRect` for panel/viewport size calculation.<br/>You can use this option to calculate the original panel size when CSS transform is applied to viewport or panel.<br/>⚠️ If panel size is not fixed integer value, there can be a 1px gap between panels.<ko>패널과 뷰포트의 크기를 계산할 때 `offsetWidth`/`offsetHeight`를 `getBoundingClientRect` 대신 사용할지 여부.<br/>패널이나 뷰포트에 CSS transform이 설정되어 있을 때 원래 패널 크기를 계산하려면 옵션을 활성화한다.<br/>⚠️ 패널의 크기가 정수로 고정되어있지 않다면 패널 사이에 1px의 공간이 생길 수 있다.</ko>
  125. * @param {boolean} [options.renderOnlyVisible=false] Whether to render visible panels only. This can dramatically increase performance when there're many panels.<ko>보이는 패널만 렌더링할지 여부를 설정한다. 패널이 많을 경우에 퍼포먼스를 크게 향상시킬 수 있다.</ko>
  126. * @param {boolean|string[]} [options.isEqualSize=false] This option indicates whether all panels have the same size(true) of first panel, or it can hold a list of class names that determines panel size.<br/>Enabling this option can increase performance while recalculating panel size.<ko>모든 패널의 크기가 동일한지(true), 혹은 패널 크기를 결정하는 패널 클래스들의 리스트.<br/>이 옵션을 설정하면 패널 크기 재설정시에 성능을 높일 수 있다.</ko>
  127. * @param {boolean} [options.isConstantSize=false] Whether all panels have a constant size that won't be changed after resize. Enabling this option can increase performance while recalculating panel size.<ko>모든 패널의 크기가 불변인지의 여부. 이 옵션을 'true'로 설정하면 패널 크기 재설정시에 성능을 높일 수 있다.</ko>
  128. * @param {boolean} [options.renderExternal=false] Whether to use external rendering. It will delegate DOM manipulation and can synchronize the rendered state by calling `sync()` method. You can use this option to use in frameworks like React, Vue, Angular, which has its states and rendering methods.<ko>외부 렌더링을 사용할 지의 여부. 이 옵션을 사용시 렌더링을 외부에 위임할 수 있고, `sync()`를 호출하여 그 상태를 동기화할 수 있다. 이 옵션을 사용하여, React, Vue, Angular 등 자체적인 상태와 렌더링 방법을 갖는 프레임워크에 대응할 수 있다.</ko>
  129. * @param {boolean} [options.resizeOnContentsReady=false] Whether to resize the Flicking after the image/video elements inside viewport are ready.<br/>Use this property to prevent wrong Flicking layout caused by dynamic image / video sizes.<ko>Flicking 내부의 이미지 / 비디오 엘리먼트들이 전부 로드되었을 때 Flicking의 크기를 재계산하기 위한 옵션.<br/>이미지 / 비디오 크기가 고정 크기가 아닐 경우 사용하여 레이아웃이 잘못되는 것을 방지할 수 있다.</ko>
  130. * @param {boolean} [options.collectStatistics=true] Whether to collect statistics on how you are using `Flicking`. These statistical data do not contain any personal information and are used only as a basis for the development of a user-friendly product.<ko>어떻게 `Flicking`을 사용하고 있는지에 대한 통계 수집 여부를 나타낸다. 이 통계자료는 개인정보를 포함하고 있지 않으며 오직 사용자 친화적인 제품으로 발전시키기 위한 근거자료로서 활용한다.</ko>
  131. */
  132. constructor(
  133. element: string | HTMLElement,
  134. options: Partial<FlickingOptions> = {},
  135. ) {
  136. super();
  137. // Set flicking wrapper user provided
  138. let wrapper: HTMLElement | null;
  139. if (isString(element)) {
  140. wrapper = document.querySelector(element);
  141. if (!wrapper) {
  142. throw new Error("Base element doesn't exist.");
  143. }
  144. } else if (element.nodeName && element.nodeType === 1) {
  145. wrapper = element;
  146. } else {
  147. throw new Error("Element should be provided in string or HTMLElement.");
  148. }
  149. this.wrapper = wrapper;
  150. // Override default options
  151. this.options = merge({}, DEFAULT_OPTIONS, options) as FlickingOptions;
  152. // Override moveType option
  153. const currentOptions = this.options;
  154. const moveType = currentOptions.moveType as MoveTypeStringOption;
  155. if (moveType in DEFAULT_MOVE_TYPE_OPTIONS) {
  156. currentOptions.moveType = DEFAULT_MOVE_TYPE_OPTIONS[moveType as keyof typeof DEFAULT_MOVE_TYPE_OPTIONS];
  157. }
  158. // Make viewport instance with panel container element
  159. this.viewport = new Viewport(this, this.options, this.triggerEvent);
  160. this.listenInput();
  161. this.listenResize();
  162. // if (this.options.collectStatistics) {
  163. // sendEvent(
  164. // "usage",
  165. // "options",
  166. // options,
  167. // );
  168. // }
  169. }
  170. /**
  171. * Move to the previous panel if it exists.
  172. * @ko 이전 패널이 존재시 해당 패널로 이동한다.
  173. * @param [duration=options.duration] Duration of the panel movement animation.(unit: ms)<ko>패널 이동 애니메이션 진행 시간.(단위: ms)</ko>
  174. * @return {eg.Flicking} The instance itself.<ko>인스턴스 자기 자신.</ko>
  175. */
  176. public prev(duration?: number): this {
  177. const currentPanel = this.getCurrentPanel();
  178. const currentState = this.viewport.stateMachine.getState();
  179. if (currentPanel && currentState.type === STATE_TYPE.IDLE) {
  180. const prevPanel = currentPanel.prev();
  181. if (prevPanel) {
  182. prevPanel.focus(duration);
  183. }
  184. }
  185. return this;
  186. }
  187. /**
  188. * Move to the next panel if it exists.
  189. * @ko 다음 패널이 존재시 해당 패널로 이동한다.
  190. * @param [duration=options.duration] Duration of the panel movement animation(unit: ms).<ko>패널 이동 애니메이션 진행 시간.(단위: ms)</ko>
  191. * @return {eg.Flicking} The instance itself.<ko>인스턴스 자기 자신.</ko>
  192. */
  193. public next(duration?: number): this {
  194. const currentPanel = this.getCurrentPanel();
  195. const currentState = this.viewport.stateMachine.getState();
  196. if (currentPanel && currentState.type === STATE_TYPE.IDLE) {
  197. const nextPanel = currentPanel.next();
  198. if (nextPanel) {
  199. nextPanel.focus(duration);
  200. }
  201. }
  202. return this;
  203. }
  204. /**
  205. * Move to the panel of given index.
  206. * @ko 주어진 인덱스에 해당하는 패널로 이동한다.
  207. * @param index The index number of the panel to move.<ko>이동할 패널의 인덱스 번호.</ko>
  208. * @param duration [duration=options.duration] Duration of the panel movement.(unit: ms)<ko>패널 이동 애니메이션 진행 시간.(단위: ms)</ko>
  209. * @return {eg.Flicking} The instance itself.<ko>인스턴스 자기 자신.</ko>
  210. */
  211. public moveTo(index: number, duration?: number): this {
  212. const viewport = this.viewport;
  213. const panel = viewport.panelManager.get(index);
  214. const state = viewport.stateMachine.getState();
  215. if (!panel || state.type !== STATE_TYPE.IDLE) {
  216. return this;
  217. }
  218. const anchorPosition = panel.getAnchorPosition();
  219. const hangerPosition = viewport.getHangerPosition();
  220. let targetPanel = panel;
  221. if (this.options.circular) {
  222. const scrollAreaSize = viewport.getScrollAreaSize();
  223. // Check all three possible locations, find the nearest position among them.
  224. const possiblePositions = [
  225. anchorPosition - scrollAreaSize,
  226. anchorPosition,
  227. anchorPosition + scrollAreaSize,
  228. ];
  229. const nearestPosition = possiblePositions.reduce((nearest, current) => {
  230. return (Math.abs(current - hangerPosition) < Math.abs(nearest - hangerPosition))
  231. ? current
  232. : nearest;
  233. }, Infinity) - panel.getRelativeAnchorPosition();
  234. const identicals = panel.getIdenticalPanels();
  235. const offset = nearestPosition - anchorPosition;
  236. if (offset > 0) {
  237. // First cloned panel is nearest
  238. targetPanel = identicals[1];
  239. } else if (offset < 0) {
  240. // Last cloned panel is nearest
  241. targetPanel = identicals[identicals.length - 1];
  242. }
  243. targetPanel = targetPanel.clone(targetPanel.getCloneIndex(), true);
  244. targetPanel.setPosition(nearestPosition);
  245. }
  246. const currentIndex = this.getIndex();
  247. if (hangerPosition === targetPanel.getAnchorPosition() && currentIndex === index) {
  248. return this;
  249. }
  250. const eventType = panel.getIndex() === viewport.getCurrentIndex()
  251. ? ""
  252. : EVENTS.CHANGE;
  253. viewport.moveTo(
  254. targetPanel,
  255. viewport.findEstimatedPosition(targetPanel),
  256. eventType,
  257. null,
  258. duration,
  259. );
  260. return this;
  261. }
  262. /**
  263. * Return index of the current panel. `-1` if no panel exists.
  264. * @ko 현재 패널의 인덱스 번호를 반환한다. 패널이 하나도 없을 경우 `-1`을 반환한다.
  265. * @return Current panel's index, zero-based integer.<ko>현재 패널의 인덱스 번호. 0부터 시작하는 정수.</ko>
  266. */
  267. public getIndex(): number {
  268. return this.viewport.getCurrentIndex();
  269. }
  270. /**
  271. * Return the wrapper element user provided in constructor.
  272. * @ko 사용자가 생성자에서 제공한 래퍼 엘리먼트를 반환한다.
  273. * @return Wrapper element user provided.<ko>사용자가 제공한 래퍼 엘리먼트.</ko>
  274. */
  275. public getElement(): HTMLElement {
  276. return this.wrapper;
  277. }
  278. /**
  279. * Return the viewport element's size.
  280. * @ko 뷰포트 엘리먼트의 크기를 반환한다.
  281. * @return Width if horizontal: true, height if horizontal: false
  282. */
  283. public getSize(): number {
  284. return this.viewport.getSize();
  285. }
  286. /**
  287. * Return current panel. `null` if no panel exists.
  288. * @ko 현재 패널을 반환한다. 패널이 하나도 없을 경우 `null`을 반환한다.
  289. * @return Current panel.<ko>현재 패널.</ko>
  290. */
  291. public getCurrentPanel(): FlickingPanel | null {
  292. const viewport = this.viewport;
  293. const panel = viewport.getCurrentPanel();
  294. return panel
  295. ? panel
  296. : null;
  297. }
  298. /**
  299. * Return the panel of given index. `null` if it doesn't exists.
  300. * @ko 주어진 인덱스에 해당하는 패널을 반환한다. 해당 패널이 존재하지 않을 시 `null`이다.
  301. * @return Panel of given index.<ko>주어진 인덱스에 해당하는 패널.</ko>
  302. */
  303. public getPanel(index: number): FlickingPanel | null {
  304. const viewport = this.viewport;
  305. const panel = viewport.panelManager.get(index);
  306. return panel
  307. ? panel
  308. : null;
  309. }
  310. /**
  311. * Return all panels.
  312. * @ko 모든 패널들을 반환한다.
  313. * @param - Should include cloned panels or not.<ko>복사된 패널들을 포함할지의 여부.</ko>
  314. * @return All panels.<ko>모든 패널들.</ko>
  315. */
  316. public getAllPanels(includeClone?: boolean): FlickingPanel[] {
  317. const viewport = this.viewport;
  318. const panelManager = viewport.panelManager;
  319. const panels = includeClone
  320. ? panelManager.allPanels()
  321. : panelManager.originalPanels();
  322. return panels
  323. .filter(panel => !!panel);
  324. }
  325. /**
  326. * Return the panels currently shown in viewport area.
  327. * @ko 현재 뷰포트 영역에서 보여지고 있는 패널들을 반환한다.
  328. * @return Panels currently shown in viewport area.<ko>현재 뷰포트 영역에 보여지는 패널들</ko>
  329. */
  330. public getVisiblePanels(): FlickingPanel[] {
  331. return this.viewport.calcVisiblePanels();
  332. }
  333. /**
  334. * Return length of original panels.
  335. * @ko 원본 패널의 개수를 반환한다.
  336. * @return Length of original panels.<ko>원본 패널의 개수</ko>
  337. */
  338. public getPanelCount(): number {
  339. return this.viewport.panelManager.getPanelCount();
  340. }
  341. /**
  342. * Return how many groups of clones are created.
  343. * @ko 몇 개의 클론 그룹이 생성되었는지를 반환한다.
  344. * @return Length of cloned panel groups.<ko>클론된 패널 그룹의 개수</ko>
  345. */
  346. public getCloneCount(): number {
  347. return this.viewport.panelManager.getCloneCount();
  348. }
  349. /**
  350. * Get maximum panel index for `infinite` mode.
  351. * @ko `infinite` 모드에서 적용되는 추가 가능한 패널의 최대 인덱스 값을 반환한다.
  352. * @see {@link eg.Flicking.FlickingOptions}
  353. * @return Maximum index of panel that can be added.<ko>최대 추가 가능한 패널의 인덱스.</ko>
  354. */
  355. public getLastIndex(): number {
  356. return this.viewport.panelManager.getLastIndex();
  357. }
  358. /**
  359. * Set maximum panel index for `infinite' mode.<br>[needPanel]{@link eg.Flicking#events:needPanel} won't be triggered anymore when last panel's index reaches it.<br>Also, you can't add more panels after it.
  360. * @ko `infinite` 모드에서 적용되는 패널의 최대 인덱스를 설정한다.<br>마지막 패널의 인덱스가 설정한 값에 도달할 경우 더 이상 [needPanel]{@link eg.Flicking#events:needPanel} 이벤트가 발생되지 않는다.<br>또한, 설정한 인덱스 이후로 새로운 패널을 추가할 수 없다.
  361. * @param - Maximum panel index.
  362. * @see {@link eg.Flicking.FlickingOptions}
  363. * @return {eg.Flicking} The instance itself.<ko>인스턴스 자기 자신.</ko>
  364. */
  365. public setLastIndex(index: number): this {
  366. this.viewport.setLastIndex(index);
  367. return this;
  368. }
  369. /**
  370. * Return panel movement animation.
  371. * @ko 현재 패널 이동 애니메이션이 진행 중인지를 반환한다.
  372. * @return Is animating or not.<ko>애니메이션 진행 여부.</ko>
  373. */
  374. public isPlaying(): boolean {
  375. return this.viewport.stateMachine.getState().playing;
  376. }
  377. /**
  378. * Unblock input devices.
  379. * @ko 막았던 입력 장치로부터의 입력을 푼다.
  380. * @return {eg.Flicking} The instance itself.<ko>인스턴스 자기 자신.</ko>
  381. */
  382. public enableInput(): this {
  383. this.viewport.enable();
  384. return this;
  385. }
  386. /**
  387. * Block input devices.
  388. * @ko 입력 장치로부터의 입력을 막는다.
  389. * @return {eg.Flicking} The instance itself.<ko>인스턴스 자기 자신.</ko>
  390. */
  391. public disableInput(): this {
  392. this.viewport.disable();
  393. return this;
  394. }
  395. /**
  396. * Get current flicking status. You can restore current state by giving returned value to [setStatus()]{@link eg.Flicking#setStatus}.
  397. * @ko 현재 상태 값을 반환한다. 반환받은 값을 [setStatus()]{@link eg.Flicking#setStatus} 메소드의 인자로 지정하면 현재 상태를 복원할 수 있다.
  398. * @return An object with current status value information.<ko>현재 상태값 정보를 가진 객체.</ko>
  399. */
  400. public getStatus(): FlickingStatus {
  401. const viewport = this.viewport;
  402. const panels = viewport.panelManager.originalPanels()
  403. .filter(panel => !!panel)
  404. .map(panel => {
  405. return {
  406. html: panel.getElement().outerHTML,
  407. index: panel.getIndex(),
  408. position: panel.getPosition(),
  409. };
  410. });
  411. return {
  412. index: viewport.getCurrentIndex(),
  413. panels,
  414. position: viewport.getCameraPosition(),
  415. };
  416. }
  417. /**
  418. * Restore to the state of the `status`.
  419. * @ko `status`의 상태로 복원한다.
  420. * @param status Status value to be restored. You can specify the return value of the [getStatus()]{@link eg.Flicking#getStatus} method.<ko>복원할 상태 값. [getStatus()]{@link eg.Flicking#getStatus}메서드의 반환값을 지정하면 된다.</ko>
  421. */
  422. public setStatus(status: FlickingStatus): void {
  423. this.viewport.restore(status);
  424. }
  425. /**
  426. * Add plugins that can have different effects on Flicking.
  427. * @ko 플리킹에 다양한 효과를 부여할 수 있는 플러그인을 추가한다.
  428. * @param - The plugin(s) to add.<ko>추가할 플러그인(들).</ko>
  429. * @return {eg.Flicking} The instance itself.<ko>인스턴스 자기 자신.</ko>
  430. */
  431. public addPlugins(plugins: Plugin | Plugin[]) {
  432. this.viewport.addPlugins(plugins);
  433. return this;
  434. }
  435. /**
  436. * Remove plugins from Flicking.
  437. * @ko 플리킹으로부터 플러그인들을 제거한다.
  438. * @param - The plugin(s) to remove.<ko>제거 플러그인(들).</ko>
  439. * @return {eg.Flicking} The instance itself.<ko>인스턴스 자기 자신.</ko>
  440. */
  441. public removePlugins(plugins: Plugin | Plugin[]) {
  442. this.viewport.removePlugins(plugins);
  443. return this;
  444. }
  445. /**
  446. * Return the reference element and all its children to the state they were in before the instance was created. Remove all attached event handlers. Specify `null` for all attributes of the instance (including inherited attributes).
  447. * @ko 기준 요소와 그 하위 패널들을 인스턴스 생성전의 상태로 되돌린다. 부착된 모든 이벤트 핸들러를 탈거한다. 인스턴스의 모든 속성(상속받은 속성포함)에 `null`을 지정한다.
  448. * @example
  449. * const flick = new eg.Flicking("#flick");
  450. * flick.destroy();
  451. * console.log(flick.moveTo); // null
  452. */
  453. public destroy(option: Partial<DestroyOption> = {}): void {
  454. this.off();
  455. if (this.options.autoResize) {
  456. window.removeEventListener("resize", this.resize);
  457. }
  458. this.viewport.destroy(option);
  459. this.contentsReadyChecker?.destroy();
  460. // release resources
  461. for (const x in this) {
  462. (this as any)[x] = null;
  463. }
  464. }
  465. /**
  466. * Update panels to current state.
  467. * @ko 패널들을 현재 상태에 맞춰 갱신한다.
  468. * @method
  469. * @return {eg.Flicking} The instance itself.<ko>인스턴스 자기 자신.</ko>
  470. */
  471. public resize = (): this => {
  472. const viewport = this.viewport;
  473. const options = this.options;
  474. const wrapper = this.getElement();
  475. const allPanels = viewport.panelManager.allPanels();
  476. if (!options.isConstantSize) {
  477. allPanels
  478. .filter(panel => !!panel)
  479. .forEach(panel => panel.unCacheBbox());
  480. }
  481. const shouldResetElements = options.renderOnlyVisible
  482. && !options.isConstantSize
  483. && options.isEqualSize !== true;
  484. // Temporarily set parent's height to prevent scroll (#333)
  485. const parent = wrapper.parentElement!;
  486. const origStyle = parent.style.height;
  487. parent.style.height = `${parent.offsetHeight}px`;
  488. viewport.unCacheBbox();
  489. // This should be done before adding panels, to lower performance issue
  490. viewport.updateBbox();
  491. if (shouldResetElements) {
  492. viewport.appendUncachedPanelElements(allPanels as Panel[]);
  493. }
  494. viewport.resize();
  495. parent.style.height = origStyle;
  496. return this;
  497. }
  498. /**
  499. * Add new panels at the beginning of panels.
  500. * @ko 제일 앞에 새로운 패널을 추가한다.
  501. * @param element - Either HTMLElement, HTML string, or array of them.<br>It can be also HTML string of multiple elements with same depth.<ko>HTMLElement 혹은 HTML 문자열, 혹은 그것들의 배열도 가능하다.<br>또한, 같은 depth의 여러 개의 엘리먼트에 해당하는 HTML 문자열도 가능하다.</ko>
  502. * @return Array of appended panels.<ko>추가된 패널들의 배열</ko>
  503. * @example
  504. * // Suppose there were no panels at initialization
  505. * const flicking = new eg.Flicking("#flick");
  506. * flicking.replace(3, document.createElement("div")); // Add new panel at index 3
  507. * flicking.prepend("\<div\>Panel\</div\>"); // Prepended at index 2
  508. * flicking.prepend(["\<div\>Panel\</div\>", document.createElement("div")]); // Prepended at index 0, 1
  509. * flicking.prepend("\<div\>Panel\</div\>"); // Prepended at index 0, pushing every panels behind it.
  510. */
  511. public prepend(element: ElementLike | ElementLike[]): FlickingPanel[] {
  512. const viewport = this.viewport;
  513. const parsedElements = parseElement(element);
  514. const insertingIndex = Math.max(viewport.panelManager.getRange().min - parsedElements.length, 0);
  515. const prependedPanels = viewport.insert(insertingIndex, parsedElements);
  516. this.checkContentsReady(prependedPanels);
  517. return prependedPanels;
  518. }
  519. /**
  520. * Add new panels at the end of panels.
  521. * @ko 제일 끝에 새로운 패널을 추가한다.
  522. * @param element - Either HTMLElement, HTML string, or array of them.<br>It can be also HTML string of multiple elements with same depth.<ko>HTMLElement 혹은 HTML 문자열, 혹은 그것들의 배열도 가능하다.<br>또한, 같은 depth의 여러 개의 엘리먼트에 해당하는 HTML 문자열도 가능하다.</ko>
  523. * @return Array of appended panels.<ko>추가된 패널들의 배열</ko>
  524. * @example
  525. * // Suppose there were no panels at initialization
  526. * const flicking = new eg.Flicking("#flick");
  527. * flicking.append(document.createElement("div")); // Appended at index 0
  528. * flicking.append("\<div\>Panel\</div\>"); // Appended at index 1
  529. * flicking.append(["\<div\>Panel\</div\>", document.createElement("div")]); // Appended at index 2, 3
  530. * // Even this is possible
  531. * flicking.append("\<div\>Panel 1\</div\>\<div\>Panel 2\</div\>"); // Appended at index 4, 5
  532. */
  533. public append(element: ElementLike | ElementLike[]): FlickingPanel[] {
  534. const viewport = this.viewport;
  535. const appendedPanels = viewport.insert(viewport.panelManager.getRange().max + 1, element);
  536. this.checkContentsReady(appendedPanels);
  537. return appendedPanels;
  538. }
  539. /**
  540. * Replace existing panels with new panels from given index. If target index is empty, add new panel at target index.
  541. * @ko 주어진 인덱스로부터의 패널들을 새로운 패널들로 교체한다. 인덱스에 해당하는 자리가 비어있다면, 새로운 패널을 해당 자리에 집어넣는다.
  542. * @param index - Start index to replace new panels.<ko>새로운 패널들로 교체할 시작 인덱스</ko>
  543. * @param element - Either HTMLElement, HTML string, or array of them.<br>It can be also HTML string of multiple elements with same depth.<ko>HTMLElement 혹은 HTML 문자열, 혹은 그것들의 배열도 가능하다.<br>또한, 같은 depth의 여러 개의 엘리먼트에 해당하는 HTML 문자열도 가능하다.</ko>
  544. * @return Array of created panels by replace.<ko>교체되어 새롭게 추가된 패널들의 배열</ko>
  545. * @example
  546. * // Suppose there were no panels at initialization
  547. * const flicking = new eg.Flicking("#flick");
  548. *
  549. * // This will add new panel at index 3,
  550. * // Index 0, 1, 2 is empty at this moment.
  551. * // [empty, empty, empty, PANEL]
  552. * flicking.replace(3, document.createElement("div"));
  553. *
  554. * // As index 2 was empty, this will also add new panel at index 2.
  555. * // [empty, empty, PANEL, PANEL]
  556. * flicking.replace(2, "\<div\>Panel\</div\>");
  557. *
  558. * // Index 3 was not empty, so it will replace previous one.
  559. * // It will also add new panels at index 4 and 5.
  560. * // before - [empty, empty, PANEL, PANEL]
  561. * // after - [empty, empty, PANEL, NEW_PANEL, NEW_PANEL, NEW_PANEL]
  562. * flicking.replace(3, ["\<div\>Panel\</div\>", "\<div\>Panel\</div\>", "\<div\>Panel\</div\>"])
  563. */
  564. public replace(index: number, element: ElementLike | ElementLike[]): FlickingPanel[] {
  565. const replacedPanels = this.viewport.replace(index, element);
  566. this.checkContentsReady(replacedPanels);
  567. return replacedPanels;
  568. }
  569. /**
  570. * Remove panel at target index. This will decrease index of panels behind it.
  571. * @ko `index`에 해당하는 자리의 패널을 제거한다. 수행시 `index` 이후의 패널들의 인덱스가 감소된다.
  572. * @param index - Index of panel to remove.<ko>제거할 패널의 인덱스</ko>
  573. * @param {number} [deleteCount=1] - Number of panels to remove from index.<ko>`index` 이후로 제거할 패널의 개수.</ko>
  574. * @return Array of removed panels<ko>제거된 패널들의 배열</ko>
  575. */
  576. public remove(index: number, deleteCount: number = 1): FlickingPanel[] {
  577. return this.viewport.remove(index, deleteCount);
  578. }
  579. /**
  580. * Get indexes to render. Should be used with `renderOnlyVisible` option.
  581. * `beforeSync` should be called before this method for a correct result.
  582. * @private
  583. * @ko 렌더링이 필요한 인덱스들을 반환한다. `renderOnlyVisible` 옵션과 함께 사용해야 한다. 정확한 결과를 위해선 `beforeSync`를 이전에 호출해야만 합니다.
  584. * @param - Info object of how panel infos are changed.<ko>패널 정보들의 변경 정보를 담는 오브젝트.</ko>
  585. * @return Array of indexes to render.<ko>렌더링할 인덱스의 배열</ko>
  586. */
  587. public getRenderingIndexes(diffResult: DiffResult<any>): number[] {
  588. const viewport = this.viewport;
  589. const visiblePanels = viewport.getVisiblePanels();
  590. const maintained = diffResult.maintained.reduce((values: {[key: number]: number}, [before, after]) => {
  591. values[after] = before;
  592. return values;
  593. }, {});
  594. const panelCount = diffResult.list.length;
  595. const added = diffResult.added;
  596. const getPanelAbsIndex = (panel: Panel) => {
  597. return panel.getIndex() + (panel.getCloneIndex() + 1) * panelCount;
  598. };
  599. const visibleIndexes = visiblePanels.map(panel => getPanelAbsIndex(panel))
  600. .filter(val => maintained[val % panelCount] != null);
  601. const renderingPanels = [...visibleIndexes, ...added];
  602. const allPanels = viewport.panelManager.allPanels();
  603. viewport.setVisiblePanels(renderingPanels.map(index => allPanels[index]));
  604. return renderingPanels;
  605. }
  606. /**
  607. * Synchronize info of panels instance with info given by external rendering.
  608. * @ko 외부 렌더링 방식에 의해 입력받은 패널의 정보와 현재 플리킹이 갖는 패널 정보를 동기화한다.
  609. * @private
  610. * @param - Info object of how panel infos are changed.<ko>패널 정보들의 변경 정보를 담는 오브젝트.</ko>
  611. * @param - Whether called from sync method <ko> sync 메소드로부터 호출됐는지 여부 </ko>
  612. */
  613. public beforeSync(diffInfo: BeforeSyncResult) {
  614. const { maintained, added, changed, removed } = diffInfo;
  615. const viewport = this.viewport;
  616. const panelManager = viewport.panelManager;
  617. const isCircular = this.options.circular;
  618. const currentPanel = viewport.getCurrentPanel();
  619. const cloneCount = panelManager.getCloneCount();
  620. const prevClonedPanels = panelManager.clonedPanels();
  621. // Update visible panels
  622. const newVisiblePanels = viewport.getVisiblePanels()
  623. .filter(panel => findIndex(removed, index => {
  624. return index === panel.getIndex();
  625. }) < 0);
  626. viewport.setVisiblePanels(newVisiblePanels);
  627. // Did not changed at all
  628. if (
  629. added.length <= 0
  630. && removed.length <= 0
  631. && changed.length <= 0
  632. && cloneCount === prevClonedPanels.length
  633. ) {
  634. return this;
  635. }
  636. const prevOriginalPanels = panelManager.originalPanels();
  637. const newPanels: Panel[] = [];
  638. const newClones: Panel[][] = counter(cloneCount).map(() => []);
  639. maintained.forEach(([beforeIdx, afterIdx]) => {
  640. newPanels[afterIdx] = prevOriginalPanels[beforeIdx];
  641. newPanels[afterIdx].setIndex(afterIdx);
  642. });
  643. added.forEach(addIndex => {
  644. newPanels[addIndex] = new Panel(null, addIndex, this.viewport);
  645. });
  646. if (isCircular) {
  647. counter(cloneCount).forEach(groupIndex => {
  648. const prevCloneGroup = prevClonedPanels[groupIndex];
  649. const newCloneGroup = newClones[groupIndex];
  650. maintained.forEach(([beforeIdx, afterIdx]) => {
  651. newCloneGroup[afterIdx] = prevCloneGroup
  652. ? prevCloneGroup[beforeIdx]
  653. : newPanels[afterIdx].clone(groupIndex, false);
  654. newCloneGroup[afterIdx].setIndex(afterIdx);
  655. });
  656. added.forEach(addIndex => {
  657. const newPanel = newPanels[addIndex];
  658. newCloneGroup[addIndex] = newPanel.clone(groupIndex, false);
  659. });
  660. });
  661. }
  662. added.forEach(index => { viewport.updateCheckedIndexes({ min: index, max: index }); });
  663. removed.forEach(index => { viewport.updateCheckedIndexes({ min: index - 1, max: index + 1 }); });
  664. const checkedIndexes = viewport.getCheckedIndexes();
  665. checkedIndexes.forEach(([min, max], idx) => {
  666. // Push checked indexes backward
  667. const pushedIndex = added.filter(index => index < min && panelManager.has(index)).length
  668. - removed.filter(index => index < min).length;
  669. checkedIndexes.splice(idx, 1, [min + pushedIndex, max + pushedIndex]);
  670. });
  671. // Only effective only when there are least one panel which have changed its index
  672. if (changed.length > 0) {
  673. // Removed checked index by changed ones after pushing
  674. maintained.forEach(([, next]) => { viewport.updateCheckedIndexes({ min: next, max: next }); });
  675. }
  676. panelManager.replacePanels(newPanels, newClones);
  677. if (!currentPanel && newPanels.length > 0) {
  678. viewport.setCurrentPanel(newPanels[0]);
  679. } else if (newPanels.length <= 0) {
  680. viewport.setCurrentPanel(undefined);
  681. }
  682. this.isPanelChangedAtBeforeSync = true;
  683. }
  684. /**
  685. * Synchronize info of panels with DOM info given by external rendering.
  686. * @ko 외부 렌더링 방식에 의해 입력받은 DOM의 정보와 현재 플리킹이 갖는 패널 정보를 동기화 한다.
  687. * @private
  688. * @param - Info object of how panel elements are changed.<ko>패널의 DOM 요소들의 변경 정보를 담는 오브젝트.</ko>
  689. */
  690. public sync(diffInfo: SyncResult): this {
  691. const { list, maintained, added, changed, removed } = diffInfo;
  692. // Did not changed at all
  693. if (added.length <= 0 && removed.length <= 0 && changed.length <= 0) {
  694. return this;
  695. }
  696. const viewport = this.viewport;
  697. const { renderOnlyVisible, circular } = this.options;
  698. const panelManager = viewport.panelManager;
  699. if (!renderOnlyVisible) {
  700. const indexRange = panelManager.getRange();
  701. let beforeDiffInfo: BeforeSyncResult = diffInfo;
  702. if (circular) {
  703. const prevOriginalPanelCount = indexRange.max;
  704. const originalPanelCount = (list.length / (panelManager.getCloneCount() + 1)) >> 0;
  705. const originalAdded = added.filter(index => index < originalPanelCount);
  706. const originalRemoved = removed.filter(index => index <= prevOriginalPanelCount);
  707. const originalMaintained = maintained.filter(([beforeIdx]) => beforeIdx <= prevOriginalPanelCount);
  708. const originalChanged = changed.filter(([beforeIdx]) => beforeIdx <= prevOriginalPanelCount);
  709. beforeDiffInfo = {
  710. added: originalAdded,
  711. maintained: originalMaintained,
  712. removed: originalRemoved,
  713. changed: originalChanged,
  714. };
  715. }
  716. this.beforeSync(beforeDiffInfo);
  717. }
  718. const visiblePanels = renderOnlyVisible
  719. ? viewport.getVisiblePanels()
  720. : this.getAllPanels(true);
  721. added.forEach(addedIndex => {
  722. const addedElement = list[addedIndex];
  723. const beforePanel = visiblePanels[addedIndex] as Panel;
  724. beforePanel.setElement(addedElement);
  725. // As it can be 0
  726. beforePanel.unCacheBbox();
  727. });
  728. if (this.isPanelChangedAtBeforeSync) {
  729. // Reset visible panels
  730. viewport.setVisiblePanels([]);
  731. this.isPanelChangedAtBeforeSync = false;
  732. }
  733. viewport.resize();
  734. return this;
  735. }
  736. private listenInput(): void {
  737. const flicking = this;
  738. const viewport = flicking.viewport;
  739. const stateMachine = viewport.stateMachine;
  740. // Set event context
  741. flicking.eventContext = {
  742. flicking,
  743. viewport: flicking.viewport,
  744. transitTo: stateMachine.transitTo,
  745. triggerEvent: flicking.triggerEvent,
  746. moveCamera: flicking.moveCamera,
  747. stopCamera: viewport.stopCamera,
  748. };
  749. const handlers = {};
  750. for (const key in AXES_EVENTS) {
  751. const eventType = AXES_EVENTS[key];
  752. handlers[eventType] = (e: any) => stateMachine.fire(eventType, e, flicking.eventContext);
  753. }
  754. // Connect Axes instance with PanInput
  755. flicking.viewport.connectAxesHandler(handlers);
  756. }
  757. private listenResize(): void {
  758. const options = this.options;
  759. if (options.autoResize) {
  760. window.addEventListener("resize", this.resize);
  761. }
  762. if (options.resizeOnContentsReady) {
  763. const contentsReadyChecker = new ImReady();
  764. contentsReadyChecker.on("preReady", () => {
  765. this.resize();
  766. });
  767. contentsReadyChecker.on("readyElement", e => {
  768. if (e.hasLoading && e.isPreReadyOver) {
  769. this.resize();
  770. }
  771. });
  772. contentsReadyChecker.on("error", e => {
  773. this.trigger(EVENTS.CONTENT_ERROR, {
  774. type: EVENTS.CONTENT_ERROR,
  775. element: e.element,
  776. });
  777. });
  778. contentsReadyChecker.check([this.wrapper]);
  779. this.contentsReadyChecker = contentsReadyChecker;
  780. }
  781. }
  782. private triggerEvent = <T extends FlickingEvent>(
  783. eventName: ValueOf<Omit<EventType, "VISIBLE_CHANGE">>, // visibleChange event has no common event definition from other events
  784. axesEvent: any,
  785. isTrusted: boolean,
  786. params: Partial<T> = {},
  787. ): TriggerCallback => {
  788. const viewport = this.viewport;
  789. let canceled: boolean = true;
  790. // Ignore events before viewport is initialized
  791. if (viewport) {
  792. const state = viewport.stateMachine.getState();
  793. const { prev, next } = viewport.getScrollArea();
  794. const pos = viewport.getCameraPosition();
  795. let progress = getProgress(pos, [prev, prev, next]);
  796. if (this.options.circular) {
  797. progress %= 1;
  798. }
  799. canceled = !super.trigger(eventName, merge({
  800. type: eventName,
  801. index: this.getIndex(),
  802. panel: this.getCurrentPanel(),
  803. direction: state.direction,
  804. holding: state.holding,
  805. progress,
  806. axesEvent,
  807. isTrusted,
  808. }, params) as FlickingEvent);
  809. }
  810. return {
  811. onSuccess(callback: () => void): TriggerCallback {
  812. if (!canceled) {
  813. callback();
  814. }
  815. return this;
  816. },
  817. onStopped(callback: () => void): TriggerCallback {
  818. if (canceled) {
  819. callback();
  820. }
  821. return this;
  822. },
  823. } as TriggerCallback;
  824. }
  825. // Return result of "move" event triggered
  826. private moveCamera = (axesEvent: any): TriggerCallback => {
  827. const viewport = this.viewport;
  828. const state = viewport.stateMachine.getState();
  829. const options = this.options;
  830. const pos = axesEvent.pos.flick;
  831. const previousPosition = viewport.getCameraPosition();
  832. if (axesEvent.isTrusted && state.holding) {
  833. const inputOffset = options.horizontal
  834. ? axesEvent.inputEvent.offsetX
  835. : axesEvent.inputEvent.offsetY;
  836. const isNextDirection = inputOffset < 0;
  837. let cameraChange = pos - previousPosition;
  838. const looped = isNextDirection === (pos < previousPosition);
  839. if (options.circular && looped) {
  840. // Reached at max/min range of axes
  841. const scrollAreaSize = viewport.getScrollAreaSize();
  842. cameraChange = (cameraChange > 0 ? -1 : 1) * (scrollAreaSize - Math.abs(cameraChange));
  843. }
  844. const currentDirection = cameraChange === 0
  845. ? state.direction
  846. : cameraChange > 0
  847. ? DIRECTION.NEXT
  848. : DIRECTION.PREV;
  849. state.direction = currentDirection;
  850. }
  851. state.delta += axesEvent.delta.flick;
  852. viewport.moveCamera(pos, axesEvent);
  853. return this.triggerEvent(EVENTS.MOVE, axesEvent, axesEvent.isTrusted)
  854. .onStopped(() => {
  855. // Undo camera movement
  856. viewport.moveCamera(previousPosition, axesEvent);
  857. });
  858. }
  859. private checkContentsReady(panels: FlickingPanel[]) {
  860. this.contentsReadyChecker?.check(panels.map(panel => panel.getElement()));
  861. }
  862. }
  863. export default Flicking;
comments powered by Disqus