Source: rotate.js

  1. /**
  2. * Copyright (c) 2015 NAVER Corp.
  3. * egjs projects are licensed under the MIT license
  4. */
  5. import {window, document} from "./browser";
  6. export default (() => {
  7. let beforeScreenWidth = -1;
  8. let beforeVertical = null;
  9. const USER_LISTENERS = []; // user's event listener
  10. const agent = (() => {
  11. const ua = window.navigator.userAgent;
  12. const match = ua.match(/(iPhone OS|CPU OS|Android)\s([^\s;-]+)/); // fetch Android & iOS env only
  13. const res = {
  14. os: "",
  15. version: "",
  16. ua
  17. };
  18. if (match) {
  19. res.os = match[1].replace(/(?:CPU|iPhone)\sOS/, "ios").toLowerCase();
  20. res.version = match[2].replace(/\D/g, ".");
  21. }
  22. return res;
  23. })();
  24. const isMobile = /android|ios/.test(agent.os) || /Mobi/.test(agent.ua);
  25. // for non-mobile, will return an empty function methods
  26. if (!isMobile) {
  27. const fn = () => false;
  28. return {
  29. on: fn,
  30. off: fn,
  31. isVertical: fn
  32. };
  33. }
  34. /**
  35. * Return event name string for orientationChange according browser support
  36. */
  37. const ORIENTATION_CHANGE_EVENT = (() => {
  38. let type;
  39. /**
  40. * Some platform/broswer returns previous widht/height state value. For workaround, give some delays.
  41. *
  42. * Android bug:
  43. * - Andorid 2.3 - Has orientationchange with bug. Needs 500ms delay.
  44. *
  45. * Note: Samsung's branded Android 2.3
  46. * When check orientationchange using resize event, could cause browser crash if user binds resize event on window
  47. *
  48. * - Android 2.2 - orientationchange fires twice(at first time width/height are not updated, but second returns well)
  49. * - Lower than 2.2 - use resize event
  50. *
  51. * InApp bug:
  52. * - Set 200ms delay
  53. */
  54. if ((agent.os === "android" && agent.version === "2.1")) {
  55. type = "resize";
  56. } else {
  57. type = "onorientationchange" in window ? "orientationchange" : "resize";
  58. }
  59. return type;
  60. })();
  61. /**
  62. * When viewport orientation is portrait, return true otherwise false
  63. */
  64. function isVertical() {
  65. let screenWidth;
  66. let degree;
  67. let vertical;
  68. if (ORIENTATION_CHANGE_EVENT === "resize") {
  69. screenWidth = document.documentElement.clientWidth;
  70. if (beforeScreenWidth === -1) { // first call isVertical
  71. vertical = screenWidth < document.documentElement.clientHeight;
  72. } else {
  73. if (screenWidth < beforeScreenWidth) {
  74. vertical = true;
  75. } else if (screenWidth === beforeScreenWidth) {
  76. vertical = beforeVertical;
  77. } else {
  78. vertical = false;
  79. }
  80. }
  81. } else {
  82. degree = window.orientation;
  83. if (degree === 0 || degree === 180) {
  84. vertical = true;
  85. } else if (degree === 90 || degree === -90) {
  86. vertical = false;
  87. }
  88. }
  89. return vertical;
  90. }
  91. /**
  92. * Trigger rotate event
  93. */
  94. function triggerRotate(e) {
  95. const currentVertical = isVertical();
  96. if (isMobile) {
  97. if (beforeVertical !== currentVertical) {
  98. beforeVertical = currentVertical;
  99. beforeScreenWidth = document.documentElement.clientWidth;
  100. USER_LISTENERS.forEach(v => v(e, {
  101. isVertical: beforeVertical
  102. }));
  103. }
  104. }
  105. }
  106. /**
  107. * Trigger event handler
  108. */
  109. function handler(e) {
  110. let rotateTimer = null;
  111. if (ORIENTATION_CHANGE_EVENT === "resize") {
  112. window.setTimeout(() => triggerRotate(e), 0);
  113. } else {
  114. if (agent.os === "android") {
  115. const screenWidth = document.documentElement.clientWidth;
  116. if (e.type === "orientationchange" && screenWidth === beforeScreenWidth) {
  117. window.setTimeout(() => handler(e), 500);
  118. // When width value wasn't changed after firing orientationchange, then call handler again after 300ms.
  119. return false;
  120. }
  121. }
  122. rotateTimer && window.clearTimeout(rotateTimer);
  123. rotateTimer = window.setTimeout(() => triggerRotate(e), 300);
  124. }
  125. return undefined;
  126. }
  127. /**
  128. * Tiny custom rotate event binder.
  129. * > **NOTE:**
  130. * > - It works for mobile environment only.
  131. * > - For non-mobile environment, every methods will return 'false'.
  132. *
  133. * > **참고:**
  134. * > - 모바일 환경에서만 동작 합니다.
  135. * > - 비모바일 환경에서는 모든 메서드들은 'false'를 반환합니다.
  136. * @ko 기기 회전에 따른 rotate 커스텀 이벤트 바인더
  137. * @namespace eg.rotate
  138. * @param {Event} e Native event object<ko>네이티브 이벤트 객체</ko>
  139. * @param {Object} info The object of data to be sent when the event is fired<ko>이벤트가 발생할 때 전달되는 데이터 객체</ko>
  140. * @param {Boolean} info.isVertical The orientation of the device (true: portrait, false: landscape) <ko>기기의 화면 방향(true: 수직 방향, false: 수평 방향)</ko>
  141. * @support { "ios" : "7+", "an" : "2.1+ (except 3.x)"}
  142. * @example
  143. * var handler = function(e, info){
  144. * info.isVertical;
  145. * }
  146. * // bind
  147. * eg.rotate.on(handler);
  148. *
  149. * // unbind
  150. * eg.rotate.off(handler);
  151. *
  152. * // unbind all event attached (call without listener param)
  153. * eg.rotate.off();
  154. */
  155. return {
  156. /**
  157. * Bind rotate event
  158. * @ko rotate 이벤트 바인딩
  159. * @memberof eg.rotate
  160. * @static
  161. * @param {Function} listener listener function <ko>이벤트 핸들러 함수</ko>
  162. */
  163. on(listener) {
  164. if (typeof(listener) !== "function") {
  165. return;
  166. }
  167. beforeVertical = isVertical();
  168. beforeScreenWidth = document.documentElement.clientWidth;
  169. USER_LISTENERS.push(listener);
  170. // only attach once
  171. USER_LISTENERS.length === 1 &&
  172. window.addEventListener(ORIENTATION_CHANGE_EVENT, handler);
  173. },
  174. /**
  175. * Unbind rotate event
  176. * Without param, will unbind all binded listeners
  177. * @ko rotate 이벤트 바인딩 해제. 파라미터 없이 호출되는 경우, 바인딩된 모든 이벤트를 해제한다.
  178. * @memberof eg.rotate
  179. * @static
  180. * @param {Function} [listener] listener function <ko>이벤트 핸들러 함수</ko>
  181. */
  182. off(listener) {
  183. if (typeof(listener) === "function") {
  184. // remove given listener from list
  185. for (let i = 0, el; (el = USER_LISTENERS[i]); i++) {
  186. if (el === listener) {
  187. USER_LISTENERS.splice(i, 1);
  188. break;
  189. }
  190. }
  191. }
  192. // detach when the condition is met
  193. if (!listener || USER_LISTENERS.length === 0) {
  194. USER_LISTENERS.splice(0);
  195. window.removeEventListener(ORIENTATION_CHANGE_EVENT, handler);
  196. }
  197. },
  198. /**
  199. * Native event name used to detect rotate
  200. * @ko roate 이벤트를 위해 사용된 네이티브 이벤트 명
  201. * @memberof eg.rotate
  202. * @property {String} event event name <ko>이벤 명</ko>
  203. * @private
  204. */
  205. orientationChange: ORIENTATION_CHANGE_EVENT,
  206. /**
  207. * Get device is in vertical mode
  208. * @ko 화면이 수직 방향인지 여부
  209. * @memberof eg.rotate
  210. * @static
  211. * @method
  212. * @return {Boolean} The orientation of the device (true: portrait, false: landscape) <ko>기기의 화면 방향(true: 수직 방향, false: 수평 방향)</ko>
  213. * @example
  214. * eg.rotate.isVertical(); // Check if device is in portrait mode
  215. */
  216. isVertical,
  217. /**
  218. * Trigger rotate event
  219. * @memberof eg.rotate
  220. * @private
  221. */
  222. triggerRotate,
  223. /**
  224. * Event handler function
  225. * @memberof eg.rotate
  226. * @private
  227. */
  228. handler
  229. };
  230. })();
comments powered by Disqus