@xwadex/fesd 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/20240328-video4-setting.png +0 -0
- package/CHANGELOG.md +41 -0
- package/README.md +25 -0
- package/dist/assets/fesd-bundle.css +9 -0
- package/dist/assets/fesd-bundle.js +9800 -0
- package/dist/assets/fesd-bundle.js.map +1 -0
- package/index.html +25 -0
- package/package.json +23 -0
- package/prepros.config +883 -0
- package/src/fesd/anchor4/anchor4.js +179 -0
- package/src/fesd/aost4/_aost4.sass +64 -0
- package/src/fesd/aost4/aost4.js +138 -0
- package/src/fesd/article4/article4.js +280 -0
- package/src/fesd/article4/article4.md +1 -0
- package/src/fesd/category-slider/_category-slider.sass +33 -0
- package/src/fesd/category-slider/category-slider.js +332 -0
- package/src/fesd/collapse4/collapse4.js +159 -0
- package/src/fesd/detect4/detect4.js +70 -0
- package/src/fesd/dropdown4/_dropdown4.sass +185 -0
- package/src/fesd/dropdown4/cityData.js +830 -0
- package/src/fesd/dropdown4/dropdown4.js +647 -0
- package/src/fesd/image-preview/_image-preview.sass +26 -0
- package/src/fesd/image-preview/image-preview.js +209 -0
- package/src/fesd/image-validate/_image-validate.sass +21 -0
- package/src/fesd/image-validate/image-validate.js +84 -0
- package/src/fesd/marquee4/_marquee4.sass +45 -0
- package/src/fesd/marquee4/marquee4.js +371 -0
- package/src/fesd/modal4/_modal4.sass +134 -0
- package/src/fesd/modal4/modal4.js +236 -0
- package/src/fesd/modal4/modernModal.js +182 -0
- package/src/fesd/multipurpose4/_multipurpose4.sass +282 -0
- package/src/fesd/multipurpose4/multipurpose4.js +562 -0
- package/src/fesd/ripple4/_ripple4.sass +44 -0
- package/src/fesd/ripple4/ripple4.js +138 -0
- package/src/fesd/share4/share4.js +191 -0
- package/src/fesd/shared/shared.js +59 -0
- package/src/fesd/shared/utils.js +98 -0
- package/src/fesd/tab4/_tab4.sass +25 -0
- package/src/fesd/tab4/tab4.js +473 -0
- package/src/fesd/video4/README.md +3 -0
- package/src/fesd/video4/_video4.sass +117 -0
- package/src/fesd/video4/video4.js +237 -0
- package/src/fesd/video4/videoPlayer.js +195 -0
- package/src/fesd.js +53 -0
- package/src/fesd.sass +29 -0
- package/src/fesdDB.js +282 -0
- package/vite.config.js +37 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
function createRipple(e, r4) {
|
2
|
+
const button = r4;
|
3
|
+
const { color, opacity, duration } = r4.s.options;
|
4
|
+
|
5
|
+
// 可以更改點擊時生成效果樣式的結構
|
6
|
+
let ripples = document.createElement('span');
|
7
|
+
|
8
|
+
//偵測滑鼠點擊位置
|
9
|
+
let x = e.clientX - button.getBoundingClientRect().left;
|
10
|
+
let y = e.clientY - button.getBoundingClientRect().top;
|
11
|
+
|
12
|
+
ripples.style.cssText = `background: ${color};left: ${x}px;top: ${y}px;opacity: ${opacity};animation-duration: ${duration}ms`;
|
13
|
+
ripples.classList.add('circle');
|
14
|
+
|
15
|
+
// 生成
|
16
|
+
button.appendChild(ripples);
|
17
|
+
|
18
|
+
// 生成後消失
|
19
|
+
setTimeout(function () {
|
20
|
+
ripples.remove();
|
21
|
+
}, `${duration}`);
|
22
|
+
}
|
23
|
+
|
24
|
+
function removeStyle() {
|
25
|
+
document.querySelectorAll('ripple-btn').forEach(button => {
|
26
|
+
const ball = button.querySelector('.hover-ball');
|
27
|
+
button.style.setProperty('--r', '');
|
28
|
+
button.classList.remove('entered');
|
29
|
+
});
|
30
|
+
}
|
31
|
+
|
32
|
+
document.addEventListener('click', removeStyle);
|
33
|
+
|
34
|
+
class Ripple4 extends HTMLElement {
|
35
|
+
constructor() {
|
36
|
+
super();
|
37
|
+
this.initialize = false;
|
38
|
+
}
|
39
|
+
|
40
|
+
connectedCallback() {
|
41
|
+
const r4 = this;
|
42
|
+
if (r4.initialize || r4.classList.contains('r4-initialize')) return;
|
43
|
+
r4.initialize = true;
|
44
|
+
this.#create();
|
45
|
+
}
|
46
|
+
|
47
|
+
#create() {
|
48
|
+
const { SETTINGS } = fesdDB.ripple4;
|
49
|
+
this.s = {};
|
50
|
+
function attrToBoolean(str) {
|
51
|
+
let booleanVal = Boolean(str);
|
52
|
+
booleanVal = str === 'true';
|
53
|
+
return booleanVal;
|
54
|
+
}
|
55
|
+
const options = {
|
56
|
+
color: this.getAttribute('r4-color') || SETTINGS.color,
|
57
|
+
opacity: this.getAttribute('r4-opacity') || SETTINGS.opacity,
|
58
|
+
duration: Number(this.getAttribute('r4-duration')) || SETTINGS.duration,
|
59
|
+
speed: Number(this.getAttribute('r4-speed')) || SETTINGS.speed,
|
60
|
+
hover: this.getAttribute('r4-hover') ? attrToBoolean(this.getAttribute('r4-hover')) : SETTINGS.hover,
|
61
|
+
click: this.getAttribute('r4-hover-click') ? attrToBoolean(this.getAttribute('r4-hover-click')) : SETTINGS.click,
|
62
|
+
};
|
63
|
+
|
64
|
+
this.s.options = options;
|
65
|
+
if (this.s.options.hover) this.classList.add('hover-btn');
|
66
|
+
|
67
|
+
this.#init();
|
68
|
+
}
|
69
|
+
|
70
|
+
#init() {
|
71
|
+
this.#ball();
|
72
|
+
this.#event();
|
73
|
+
this.classList.add('r4-initialize');
|
74
|
+
}
|
75
|
+
|
76
|
+
#ball() {
|
77
|
+
const button = this;
|
78
|
+
const ball = document.createElement('i');
|
79
|
+
ball.classList.add('hover-ball');
|
80
|
+
ball.style.transitionDuration = `${button.s.options.speed}ms`;
|
81
|
+
button.appendChild(ball);
|
82
|
+
}
|
83
|
+
|
84
|
+
#event() {
|
85
|
+
const ball = this.querySelector('i.hover-ball');
|
86
|
+
const button = this;
|
87
|
+
// ripple點擊事件
|
88
|
+
button.addEventListener('click', function (e) {
|
89
|
+
e.stopPropagation();
|
90
|
+
if (button.s.options.click) {
|
91
|
+
createRipple(e, button);
|
92
|
+
}
|
93
|
+
});
|
94
|
+
|
95
|
+
// 判斷操作事件使用 mouse 或是 touch
|
96
|
+
let operateStart = 'ontouchstart' in document.documentElement ? 'touchstart' : 'mouseenter';
|
97
|
+
let operateEnd = 'ontouchend' in document.documentElement ? 'touchend' : 'mouseleave';
|
98
|
+
// hover
|
99
|
+
button.addEventListener(operateStart, function (e) {
|
100
|
+
if (operateStart === 'touchstart') {
|
101
|
+
removeStyle();
|
102
|
+
}
|
103
|
+
if (button.s.options.hover) {
|
104
|
+
const posX = operateStart === 'mouseenter' ? Math.round(e.clientX - button.getBoundingClientRect().left) : Math.round(e.changedTouches[0].clientX - button.getBoundingClientRect().x);
|
105
|
+
const posY = operateStart === 'mouseenter' ? Math.round(e.clientY - button.getBoundingClientRect().top) : Math.round(e.changedTouches[0].clientY - button.getBoundingClientRect().y);
|
106
|
+
const { offsetWidth, offsetHeight } = button;
|
107
|
+
// 最大可覆蓋之半徑
|
108
|
+
const rippleR = Math.ceil(Math.sqrt(offsetWidth ** 2 + offsetHeight ** 2) * 2);
|
109
|
+
button.style.setProperty('--r', rippleR);
|
110
|
+
ball.style.left = posX + 'px';
|
111
|
+
ball.style.top = posY + 'px';
|
112
|
+
button.classList.add('entered');
|
113
|
+
}
|
114
|
+
});
|
115
|
+
button.addEventListener(operateEnd, function (e) {
|
116
|
+
if (operateEnd === 'touchend') return;
|
117
|
+
if (button.s.options.hover) {
|
118
|
+
const posX = operateEnd === 'mouseleave' ? Math.round(e.clientX - button.getBoundingClientRect().left) : Math.round(e.changedTouches[0].clientX - button.getBoundingClientRect().x);
|
119
|
+
const posY = operateEnd === 'mouseleave' ? Math.round(e.clientY - button.getBoundingClientRect().top) : Math.round(e.changedTouches[0].clientY - button.getBoundingClientRect().y);
|
120
|
+
button.style.setProperty('--r', '');
|
121
|
+
ball.style.left = posX + 'px';
|
122
|
+
ball.style.top = posY + 'px';
|
123
|
+
button.classList.remove('entered');
|
124
|
+
}
|
125
|
+
});
|
126
|
+
}
|
127
|
+
update() {
|
128
|
+
this.classList.remove('r4-initialize');
|
129
|
+
this.querySelector('i.hover-ball').remove();
|
130
|
+
this.#create();
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
// if (!customElements.get('ripple-btn')) {
|
135
|
+
// customElements.define('ripple-btn', Ripple4);
|
136
|
+
// }
|
137
|
+
|
138
|
+
export default Ripple4;
|
@@ -0,0 +1,191 @@
|
|
1
|
+
import { insert, isString, isElementExist, getAllElements } from './../shared/utils';
|
2
|
+
|
3
|
+
('use strict');
|
4
|
+
|
5
|
+
const getTargetUrl = type => {
|
6
|
+
// facebook
|
7
|
+
if (type == 'facebook') {
|
8
|
+
return 'https://www.facebook.com/sharer/sharer.php?u=';
|
9
|
+
}
|
10
|
+
|
11
|
+
// line
|
12
|
+
if (type == 'line') {
|
13
|
+
// desktop & mobile 網址不同
|
14
|
+
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
|
15
|
+
return 'http://line.naver.jp/R/msg/text/?';
|
16
|
+
}
|
17
|
+
return 'https://lineit.line.me/share/ui?url=';
|
18
|
+
}
|
19
|
+
|
20
|
+
// twitter
|
21
|
+
if (type == 'twitter') {
|
22
|
+
return 'https://twitter.com/intent/tweet?url=';
|
23
|
+
}
|
24
|
+
|
25
|
+
// linkedin
|
26
|
+
if (type == 'linkedin') {
|
27
|
+
return `http://www.linkedin.com/shareArticle?mini=true&title=${document.title}&source=${document.title}&url=`;
|
28
|
+
// return `https://www.linkedin.com/sharing/share-offsite/?url=`;
|
29
|
+
}
|
30
|
+
|
31
|
+
// telegram
|
32
|
+
if (type == 'telegram') {
|
33
|
+
return 'https://telegram.me/share/url?url=';
|
34
|
+
}
|
35
|
+
};
|
36
|
+
|
37
|
+
class Share4 {
|
38
|
+
constructor(el, options = {}) {
|
39
|
+
this.__storage__ = {
|
40
|
+
el,
|
41
|
+
options,
|
42
|
+
};
|
43
|
+
|
44
|
+
this.#create();
|
45
|
+
}
|
46
|
+
|
47
|
+
#create() {
|
48
|
+
const { el, options } = this.__storage__;
|
49
|
+
// 從 fesdDB 提取設定
|
50
|
+
const { SETTINGS } = fesdDB.share4;
|
51
|
+
if (!isString(el) || !isElementExist(el)) return;
|
52
|
+
|
53
|
+
this.elements = getAllElements(el);
|
54
|
+
|
55
|
+
this.options = Object.assign({}, SETTINGS, options);
|
56
|
+
|
57
|
+
this.#init();
|
58
|
+
}
|
59
|
+
|
60
|
+
#init() {
|
61
|
+
const { elements, options } = this;
|
62
|
+
|
63
|
+
elements.forEach(targets => {
|
64
|
+
targets.querySelectorAll('[share-target]').forEach(el => {
|
65
|
+
el.share = {};
|
66
|
+
el.share.instance = this;
|
67
|
+
el.share.params = options;
|
68
|
+
el.share.eventHandler = this.#trigger;
|
69
|
+
el.addEventListener('click', el.share.eventHandler);
|
70
|
+
});
|
71
|
+
});
|
72
|
+
}
|
73
|
+
|
74
|
+
#trigger() {
|
75
|
+
/** the keyword `this` in this method is pointed to the click target */
|
76
|
+
const { eventHandler, params } = this.share;
|
77
|
+
const type = this.getAttribute('share-target');
|
78
|
+
|
79
|
+
const utm = {
|
80
|
+
source: this.getAttribute('utm-source'),
|
81
|
+
medium: this.getAttribute('utm-medium'),
|
82
|
+
campaign: this.getAttribute('utm-campaign'),
|
83
|
+
};
|
84
|
+
|
85
|
+
const copy = {
|
86
|
+
success: this.getAttribute('copy-success') || params.success,
|
87
|
+
text: this.getAttribute('copy-text') || params.text,
|
88
|
+
className: this.getAttribute('copy-class') || params.className,
|
89
|
+
duration: this.getAttribute('copy-duration') || params.duration,
|
90
|
+
};
|
91
|
+
|
92
|
+
const baseUrl = encodeURIComponent(document.URL);
|
93
|
+
let shareUrl = baseUrl;
|
94
|
+
|
95
|
+
// 若為 wechat 則另開視窗掃描 QRcode
|
96
|
+
if (type == 'wechat') {
|
97
|
+
// https://api.qrserver.com/v1/create-qr-code/?data=${url}&size=300x300
|
98
|
+
window.open(`https://api.qrserver.com/v1/create-qr-code/?data=${baseUrl}&size=250x250`, 'share to wechat', 'width=300,height=300');
|
99
|
+
return;
|
100
|
+
}
|
101
|
+
|
102
|
+
// 若為 url 則複製連結
|
103
|
+
if (type == 'url') {
|
104
|
+
if (isElementExist('.copied-wrapper')) return;
|
105
|
+
|
106
|
+
document.querySelector('body').insertAdjacentHTML(insert.append, `<div class='copied-wrapper'><div class='text'>${copy.success ? copy.success : ''}</div><input id='clipboard' type='text' readonly></div>`);
|
107
|
+
|
108
|
+
const $copied = document.querySelector('.copied-wrapper');
|
109
|
+
|
110
|
+
if (copy.className) {
|
111
|
+
$copied.classList.add(copy.className);
|
112
|
+
}
|
113
|
+
$copied.style.top = this.getBoundingClientRect().top + window.scrollY + 'px';
|
114
|
+
$copied.style.left = this.getBoundingClientRect().left + this.getBoundingClientRect().width / 2 + 'px';
|
115
|
+
$copied.style.display = 'block';
|
116
|
+
|
117
|
+
const $clipboard = document.querySelector('#clipboard');
|
118
|
+
|
119
|
+
$clipboard.value = copy.text ?? window.location.href;
|
120
|
+
$clipboard.setSelectionRange(0, 9999);
|
121
|
+
$clipboard.select();
|
122
|
+
|
123
|
+
if (document.execCommand('copy')) {
|
124
|
+
// 複製對應內容
|
125
|
+
document.execCommand('copy');
|
126
|
+
// 動畫效果
|
127
|
+
const copiedWrapper = document.querySelector('.copied-wrapper');
|
128
|
+
const tooltipsText = document.querySelector('.copied-wrapper .text');
|
129
|
+
tooltipsText.style.display = 'block';
|
130
|
+
tooltipsText.style.opacity = 0;
|
131
|
+
const fadeIn = (element, duration) => {
|
132
|
+
let opacity = 0;
|
133
|
+
const interval = 50;
|
134
|
+
const gap = interval / duration;
|
135
|
+
|
136
|
+
const fading = setInterval(function () {
|
137
|
+
opacity += gap;
|
138
|
+
element.style.opacity = opacity;
|
139
|
+
|
140
|
+
if (opacity >= 1) {
|
141
|
+
clearInterval(fading);
|
142
|
+
setTimeout(function () {
|
143
|
+
fadeOut(element, 300, function () {
|
144
|
+
copiedWrapper.remove();
|
145
|
+
});
|
146
|
+
}, copy.duration);
|
147
|
+
}
|
148
|
+
}, interval);
|
149
|
+
};
|
150
|
+
const fadeOut = (element, duration, callback) => {
|
151
|
+
let opacity = 1;
|
152
|
+
const interval = 50;
|
153
|
+
const gap = interval / duration;
|
154
|
+
|
155
|
+
const fading = setInterval(() => {
|
156
|
+
opacity -= gap;
|
157
|
+
element.style.opacity = opacity;
|
158
|
+
|
159
|
+
if (opacity <= 0) {
|
160
|
+
clearInterval(fading);
|
161
|
+
element.style.display = 'none';
|
162
|
+
if (callback) callback();
|
163
|
+
}
|
164
|
+
}, interval);
|
165
|
+
};
|
166
|
+
fadeIn(tooltipsText, 300);
|
167
|
+
}
|
168
|
+
|
169
|
+
return;
|
170
|
+
}
|
171
|
+
|
172
|
+
// 其餘則使用對應網址另開新分頁
|
173
|
+
// url + '?utm_source=Facebook' + '&utm_medium=social' + '&utm_campaign=' + campaign
|
174
|
+
shareUrl = `${shareUrl}${utm.source ? `?utm_source=${utm.source}` : ''}${utm.medium ? `&utm_medium${utm.medium}` : ''}${utm.campaign ? `&utm_campaign${utm.campaign}` : ''}`;
|
175
|
+
shareUrl = shareUrl.replace('?', '%3F').replace(new RegExp('&', 'g'), '%26');
|
176
|
+
|
177
|
+
if (type == 'line') {
|
178
|
+
// line 需帶入原始網址
|
179
|
+
window.open(`${getTargetUrl(type)}${baseUrl}`);
|
180
|
+
} else {
|
181
|
+
window.open(`${getTargetUrl(type)}${shareUrl}"e=${shareUrl}`);
|
182
|
+
}
|
183
|
+
|
184
|
+
this.removeEventListener('click', eventHandler);
|
185
|
+
setTimeout(() => {
|
186
|
+
this.addEventListener('click', eventHandler);
|
187
|
+
}, 100);
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
export default Share4;
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import {
|
2
|
+
isFunction
|
3
|
+
} from './utils'
|
4
|
+
|
5
|
+
export default {
|
6
|
+
on(events, handler) {
|
7
|
+
if (!isFunction(handler)) return this;
|
8
|
+
|
9
|
+
const { __events__ } = this
|
10
|
+
|
11
|
+
events.split(' ').forEach(evt => {
|
12
|
+
if (!__events__[evt]) __events__[evt] = []
|
13
|
+
__events__[evt].push(handler)
|
14
|
+
})
|
15
|
+
|
16
|
+
return this
|
17
|
+
},
|
18
|
+
|
19
|
+
off(events, handler) {
|
20
|
+
const { __events__ } = this
|
21
|
+
|
22
|
+
events.split(' ').forEach(evt => {
|
23
|
+
if (typeof handler === 'undefined') __events__[evt] = []
|
24
|
+
else if (__events__[evt]) {
|
25
|
+
__events__[evt].forEach((evtHandler, index) => {
|
26
|
+
if (evtHandler === handler) __events__[evt].splice(index, 1)
|
27
|
+
})
|
28
|
+
}
|
29
|
+
})
|
30
|
+
|
31
|
+
return this
|
32
|
+
},
|
33
|
+
|
34
|
+
once(events, handler) {
|
35
|
+
if (!isFunction(handler)) return this;
|
36
|
+
|
37
|
+
const onceHandler = (...args) => {
|
38
|
+
this.off(events, onceHandler)
|
39
|
+
handler.apply(this, args)
|
40
|
+
}
|
41
|
+
|
42
|
+
return this.on(events, onceHandler)
|
43
|
+
},
|
44
|
+
|
45
|
+
emit(...args) {
|
46
|
+
const { __events__ } = this
|
47
|
+
|
48
|
+
const event = args[0]
|
49
|
+
const data = args.slice(1, args.length)
|
50
|
+
|
51
|
+
if (!__events__[event]) return this
|
52
|
+
|
53
|
+
__events__[event].forEach(handler => {
|
54
|
+
if (isFunction(handler)) handler.apply(this, data)
|
55
|
+
})
|
56
|
+
|
57
|
+
return this
|
58
|
+
}
|
59
|
+
}
|
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
// insertAdjacentHTML 語意化
|
3
|
+
export const insert = {
|
4
|
+
before: 'beforebegin',
|
5
|
+
after: 'afterend',
|
6
|
+
append: 'beforeend',
|
7
|
+
prepend: 'afterbegin'
|
8
|
+
}
|
9
|
+
|
10
|
+
// check value is string and not empty
|
11
|
+
export const isString = (value) => {
|
12
|
+
return typeof value === 'string' && value !== '';
|
13
|
+
};
|
14
|
+
|
15
|
+
// check value is number and not NaN
|
16
|
+
export const isNumber = (value) => {
|
17
|
+
return typeof value === 'number' && !isNaN(value);
|
18
|
+
};
|
19
|
+
|
20
|
+
// check is HTML element
|
21
|
+
export const isElement = (value) => {
|
22
|
+
return value instanceof HTMLElement;
|
23
|
+
};
|
24
|
+
|
25
|
+
// check is Node List
|
26
|
+
export const isNodeList = (value) => {
|
27
|
+
return value instanceof NodeList;
|
28
|
+
};
|
29
|
+
|
30
|
+
// check element is exist
|
31
|
+
export const isElementExist = (value) => {
|
32
|
+
return getElement(value) !== null;
|
33
|
+
};
|
34
|
+
|
35
|
+
// check value is a function
|
36
|
+
export const isFunction = (value) => {
|
37
|
+
return typeof value === 'function';
|
38
|
+
};
|
39
|
+
|
40
|
+
// if value is an element then return value, if not then query value
|
41
|
+
export const getElement = (value) => {
|
42
|
+
return isElement(value) ? value : document.querySelector(value);
|
43
|
+
};
|
44
|
+
|
45
|
+
// if value is an element then return value, if not then query value
|
46
|
+
export const getAllElements = (value) => {
|
47
|
+
return isNodeList(value) ? value : document.querySelectorAll(value);
|
48
|
+
};
|
49
|
+
|
50
|
+
// create a unique id
|
51
|
+
export const createUid = () => {
|
52
|
+
return Math.random().toString(36).substr(2, 9);
|
53
|
+
};
|
54
|
+
|
55
|
+
// parse string to HTML element
|
56
|
+
export const toHTMLElement = (str) => {
|
57
|
+
const dom = document.createElement('div');
|
58
|
+
dom.innerHTML = str;
|
59
|
+
return dom.childNodes;
|
60
|
+
};
|
61
|
+
|
62
|
+
// parse json to object
|
63
|
+
export const jsonParse = (json) => {
|
64
|
+
try {
|
65
|
+
JSON.parse(json);
|
66
|
+
} catch (e) {
|
67
|
+
return json;
|
68
|
+
}
|
69
|
+
return JSON.parse(json);
|
70
|
+
};
|
71
|
+
|
72
|
+
// get element transform X
|
73
|
+
export const getTransformX = (target) => {
|
74
|
+
const transform = getComputedStyle(target).transform;
|
75
|
+
let mat = transform.match(/^matrix3d\((.+)\)$/);
|
76
|
+
if (mat) return parseFloat(mat[1].split(', ')[12]);
|
77
|
+
mat = transform.match(/^matrix\((.+)\)$/);
|
78
|
+
return mat ? parseFloat(mat[1].split(', ')[4]) : 0;
|
79
|
+
};
|
80
|
+
|
81
|
+
// get element transform Y
|
82
|
+
export const getTransformY = (target) => {
|
83
|
+
const transform = getComputedStyle(target).transform;
|
84
|
+
let mat = transform.match(/^matrix3d\((.+)\)$/);
|
85
|
+
if (mat) return parseFloat(mat[1].split(', ')[13]);
|
86
|
+
mat = transform.match(/^matrix\((.+)\)$/);
|
87
|
+
return mat ? parseFloat(mat[1].split(', ')[5]) : 0;
|
88
|
+
};
|
89
|
+
|
90
|
+
// warn
|
91
|
+
export const warn = (target, msg) => {
|
92
|
+
console.warn(`[${target} warn]: ${msg}`);
|
93
|
+
};
|
94
|
+
|
95
|
+
// error
|
96
|
+
export const error = (target, msg) => {
|
97
|
+
console.error(`[${target} error]: ${msg}`);
|
98
|
+
};
|
@@ -0,0 +1,25 @@
|
|
1
|
+
tab-el
|
2
|
+
[t4-role="tabPanel"]
|
3
|
+
width: 100%
|
4
|
+
height: 100%
|
5
|
+
opacity: 0
|
6
|
+
&[t4-display="silde"]
|
7
|
+
// 要用slide一定要加喔 不然會壞掉
|
8
|
+
overflow: hidden
|
9
|
+
// 可自訂 - 鍵頭按鈕樣式
|
10
|
+
[t4-role="prev"],
|
11
|
+
[t4-role="next"]
|
12
|
+
opacity: 1
|
13
|
+
transition: 0.5s opacity
|
14
|
+
cursor: pointer
|
15
|
+
&:hover
|
16
|
+
opacity: 0.7
|
17
|
+
&[disabled]
|
18
|
+
opacity: 0.1
|
19
|
+
pointer-events: none
|
20
|
+
// 頁籤樣式
|
21
|
+
[aria-selected="false"]
|
22
|
+
opacity: 0.7
|
23
|
+
[aria-selected="true"]
|
24
|
+
opacity: 1
|
25
|
+
transition: 0.5s opacity
|