Modal

Generic modal window

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/384230/704110/Modal.js

  1. // ==UserScript==
  2. // @name Modal
  3. // @name:en Modal
  4. // @description Generic modal window
  5. // @description:en Generic modal window
  6. // @namespace https://greasyfork.org/users/174399
  7. // @version 0.1.0
  8. // @include *://*.mozilla.org/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function(window, undefined) {
  13. // const btn = document.body.appendChild(createSElement(`
  14. // <div style="position: fixed; bottom: 0; right: 0; width: 10%; height: 10%; background: green; z-index: 99999; display: flex;">
  15. // <label for="modal-cbox" style="flex: 1"></label>
  16. // </div>
  17. // `));
  18. // setTimeout(() => querySelector('label', btn).click(), 2000);
  19. function Modal({
  20. content: {
  21. css: contentCSS = {},
  22. } = {},
  23. header: {
  24. html: headerHTML = '',
  25. css: headerCSS = {},
  26. onClick: onClickHeader,
  27. } = {},
  28. body: {
  29. html: bodyHTML = '',
  30. css: bodyCSS = {},
  31. onClick: onClickBody,
  32. } = {},
  33. footer: {
  34. html: footerHTML = '',
  35. css: footerCSS = {},
  36. onClick: onClickFooter,
  37. } = {},
  38. withClose = true,
  39. } = {}) {
  40. const wrapper = querySelector('.modal-wrapper') || Modal.create();
  41. const content = querySelector('.modal-content', wrapper);
  42. const mheader = querySelector('.modal-header', wrapper);
  43. const mfooter = querySelector('.modal-footer', wrapper);
  44. const mbody = querySelector('.modal-body', wrapper);
  45. // html
  46. mheader.appendChild(createSElement(headerHTML));
  47. mfooter.innerHTML = footerHTML;
  48. mbody.innerHTML = bodyHTML;
  49. // style
  50. setStyle(content, contentCSS);
  51. setStyle(mheader, headerCSS);
  52. setStyle(mfooter, footerCSS);
  53. setStyle(mbody, bodyCSS);
  54. // onclick
  55. addEventListener(mheader, 'click', onClickHeader);
  56. addEventListener(mfooter, 'click', onClickFooter);
  57. addEventListener(mbody, 'click', onClickBody);
  58. // return object
  59. this.wrapper = wrapper;
  60. this.content = content;
  61. this.header = mheader;
  62. this.footer = mfooter;
  63. this.body = mbody;
  64. this.onClickHeader = onClickHeader;
  65. this.onClickFooter = onClickFooter;
  66. this.onClickBody = onClickBody;
  67. }
  68. Modal.toggle = function() {
  69. const btn = querySelector('.modal-wrapper .modal-close-background');
  70. return btn && btn.click();
  71. };
  72. Modal._open = function(visible = true) {
  73. (querySelector('.modal-wrapper #modal-cbox') || {}).checked = visible;
  74. };
  75. Modal.close = function() { Modal._open(false); };
  76. Modal.open = function() { Modal._open(true); };
  77. Modal.WRAPPER_HTML = `
  78. <div class="modal-wrapper">
  79. <input type="checkbox" checked style="display: none" id="modal-cbox" />
  80. <div class="modal-container">
  81. <label for="modal-cbox" class="modal-close-background" ></label>
  82. <div class="modal-content">
  83. <div class="modal-header">
  84. <label for="modal-cbox" title="close" class="modal-close-x"><div></div></label>
  85. </div>
  86. <div class="modal-body"></div>
  87. <div class="modal-footer"></div>
  88. </div>
  89. </div>
  90. </div>`.replace(/\s+/g, ' ').replace(/\n/g, ' ').trim();
  91. Modal.WRAPPER_CSS = `
  92. .modal-container {
  93. opacity: 0;
  94. visibility: hidden;
  95. }
  96. #modal-cbox:checked + .modal-container {
  97. position: fixed;
  98. z-index: 9999999;
  99. visibility: visible;
  100. opacity: 1;
  101. top: 0;
  102. right: 0;
  103. bottom: 0;
  104. left: 0;
  105. transition: opacity .25s;
  106. }
  107. #modal-cbox:checked + .modal-container .modal-content {
  108. bottom: 0;
  109. }
  110. .modal-content {
  111. position: absolute;
  112. background-color: gray;
  113. min-width: 400px;
  114. min-height: 100px;
  115. opacity: 1;
  116. display: flex;
  117. flex-direction: column;
  118. align-items: center;
  119. right: 0;
  120. bottom: -20%;
  121. transition: all .25s;
  122. }
  123. .modal-header {
  124. display: flex;
  125. flex-direction: row;
  126. position: relative;
  127. align-items: center;
  128. width: 100%;
  129. height: 40px;
  130. }
  131. .modal-close-x {
  132. position: absolute;
  133. right: 10px;
  134. }
  135. .modal-close-x div {
  136. display: flex;
  137. flex-direction: row;
  138. justify-content: center;
  139. }
  140. .modal-close-x,
  141. .modal-close-x div {
  142. width: 24px;
  143. height: 24px;
  144. }
  145. .modal-close-x div:after,
  146. .modal-close-x div:before {
  147. content: "";
  148. position: absolute;
  149. background: #ccc;
  150. width: 2px;
  151. height: 24px;
  152. display: block;
  153. transform: rotate(45deg);
  154. }
  155. .modal-close-x div:before {
  156. transform: rotate(-45deg);
  157. }
  158. .modal-close-background {
  159. position: absolute;
  160. background-color: black;
  161. width: 100%;
  162. height: 100%;
  163. opacity: 0.4;
  164. }
  165. `.replace(/\s+/g, ' ').replace(/\n/g, ' ').trim();
  166. Modal.create = function() {
  167. let wrapper = querySelector('.modal-wrapper');
  168. if (wrapper) {
  169. return wrapper;
  170. }
  171. if (document.readyState === 'loading') {
  172. throw new Error('you can\'t insert HTMLElement to DOMTree while document is loading');
  173. }
  174. const {
  175. body = querySelector('body'),
  176. head = querySelector('head'),
  177. } = document;
  178. if (!body) {
  179. throw new Error('document.body not found');
  180. }
  181. if (!head) {
  182. throw new Error('document.head not found');
  183. }
  184. wrapper = createSElement(Modal.WRAPPER_HTML);
  185. const style = createElement('style', Modal.WRAPPER_CSS);
  186. head.appendChild(style);
  187. return body.appendChild(wrapper);
  188. };
  189. function createSElement(html) {
  190. return createElement('div', html).firstElementChild;
  191. }
  192. function createElement(tag, html) {
  193. const elem = document.createElement(tag);
  194. if (typeof html !== 'undefined') {
  195. elem.innerHTML = html;
  196. }
  197. return elem;
  198. }
  199. function querySelector(selector, context) {
  200. return (context || document).querySelector(selector);
  201. }
  202. function querySelectorAll(selector, context = document) {
  203. return (context || document).querySelectorAll(selector);
  204. }
  205. function setStyle(elem, css, val) {
  206. if (!elem) {
  207. return;
  208. }
  209. switch (typeof css) {
  210. case 'object':
  211. Object.keys(css).map(toCamelCase).forEach(key => setStyle(elem, key, css[key]));
  212. break;
  213. case 'string':
  214. elem.style[css] = val;
  215. break;
  216. }
  217. }
  218. function toCamelCase(str) {
  219. return str.replace(/\-([a-z])/g, (match, p1) => p1.toUpperCase());
  220. }
  221. function addEventListener(elem, event, callback) {
  222. if (!elem || typeof event !== 'string' || typeof callback !== 'function') {
  223. return;
  224. }
  225. return elem.addEventListener(event, callback);
  226. }
  227. const { ESModules = {} } = window;
  228. ESModules.Modal = Modal;
  229. window.ESModules = ESModules;
  230. // Modal.create();
  231. // setTimeout(Modal.toggle, 4000);
  232. })(window);