From 74cad48f28f1296376a88e1b3b1d901e013ca875 Mon Sep 17 00:00:00 2001 From: roffy3051 Date: Wed, 23 Oct 2024 03:59:07 +0800 Subject: [PATCH] refactor(web): Improve performance --- assets/script.js | 201 +++++++++++++++++++-------------------- assets/style.css | 113 ---------------------- assets/style.less | 235 ++++++++++++++++++++++++++++++++++++++++++++++ views/index.pug | 31 +++--- 4 files changed, 347 insertions(+), 233 deletions(-) delete mode 100644 assets/style.css create mode 100644 assets/style.less diff --git a/assets/script.js b/assets/script.js index b8fbb07..896af9a 100644 --- a/assets/script.js +++ b/assets/script.js @@ -3,117 +3,114 @@ const img = document.getElementById('result'); const code = document.getElementById('code'); - btn.addEventListener('click', throttle(() => { - const $name = document.getElementById('name'), - $theme = document.getElementById('theme'), - $padding = document.getElementById('padding'), - $offset = document.getElementById('offset'), - $scale = document.getElementById('scale'), - $pixelated = document.getElementById('pixelated'), - $darkmode = document.getElementById('darkmode') + const elements = { + name: document.getElementById('name'), + theme: document.getElementById('theme'), + padding: document.getElementById('padding'), + offset: document.getElementById('offset'), + scale: document.getElementById('scale'), + pixelated: document.getElementById('pixelated'), + darkmode: document.getElementById('darkmode'), + }; - const name = $name.value.trim(); - if (!name) { - alert('Please input counter name.'); - return; - } - - party.confetti(btn, { count: party.variation.range(20, 40) }); - - const params = { - name, - theme: $theme.value || 'moebooru', - padding: $padding.value || '7', - offset: $offset.value || '0', - scale: $scale.value || '1', - pixelated: $pixelated.checked ? '1' : '0', - darkmode: $darkmode.value || 'auto', - } - - const query = new URLSearchParams(params).toString(); - const imgSrc = `${__global_data.site}/@${name}?${query}`; - img.src = imgSrc - - img.onload = () => { - img.scrollIntoView({ block: 'start', behavior: 'smooth' }); - - code.textContent = imgSrc; - code.style.visibility = 'visible'; - } - - img.onerror = () => { - fetch(imgSrc) - .then(async (res) => { - if (!res.ok) { - const { message } = await res.json(); - alert(message); - } - }) - }; - }, 500)); - - code.addEventListener('click', (e) => { - e.preventDefault() - e.stopPropagation() - - const target = e.target; - if (document.body.createTextRange) { - const range = document.body.createTextRange(); - range.moveToElementText(target); - range.select(); - } else if (window.getSelection) { - const selection = window.getSelection(); - const range = document.createRange(); - range.selectNodeContents(target); - selection.removeAllRanges(); - selection.addRange(range); - } - }) + btn.addEventListener('click', throttle(handleButtonClick, 500)); + code.addEventListener('click', selectCodeText); const mainTitle = document.querySelector('#main_title i'); const themes = document.querySelector('#themes'); const moreTheme = document.querySelector('#more_theme'); - mainTitle.addEventListener('click', throttle(() => { - party.sparkles(document.documentElement, { count: party.variation.range(40, 100) }); - }, 1000)); + mainTitle.addEventListener('click', throttle(() => party.sparkles(document.documentElement, { count: party.variation.range(40, 100) }), 1000)); + moreTheme.addEventListener('click', scrollToThemes); - moreTheme.addEventListener('click', () => { + function handleButtonClick() { + const { name, theme, padding, offset, scale, pixelated, darkmode } = elements; + const nameValue = name.value.trim(); + + if (!nameValue) { + alert('Please input counter name.'); + return; + } + + const params = { + name: nameValue, + theme: theme.value || 'moebooru', + padding: padding.value || '7', + offset: offset.value || '0', + scale: scale.value || '1', + pixelated: pixelated.checked ? '1' : '0', + darkmode: darkmode.value || 'auto' + }; + + const query = new URLSearchParams(params).toString(); + const imgSrc = `${__global_data.site}/@${nameValue}?${query}`; + + img.src = `${imgSrc}&_=${Math.random()}`; + btn.setAttribute('disabled', ''); + + img.onload = () => { + img.scrollIntoView({ block: 'start', behavior: 'smooth' }); + code.textContent = imgSrc; + code.style.visibility = 'visible'; + party.confetti(btn, { count: party.variation.range(20, 40) }); + btn.removeAttribute('disabled'); + }; + + img.onerror = async () => { + try { + const res = await fetch(imgSrc); + if (!res.ok) { + const { message } = await res.json(); + alert(message); + } + } finally { + btn.removeAttribute('disabled'); + } + }; + } + + function selectCodeText(e) { + e.preventDefault(); + e.stopPropagation(); + + const target = e.target; + const range = document.createRange(); + const selection = window.getSelection(); + + range.selectNodeContents(target); + selection.removeAllRanges(); + selection.addRange(range); + } + + function scrollToThemes() { if (!themes.hasAttribute('open')) { party.sparkles(moreTheme.querySelector('h3'), { count: party.variation.range(20, 40) }); themes.scrollIntoView({ block: 'start', behavior: 'smooth' }); } - }); + } function throttle(fn, threshold = 250) { - let last; - let deferTimer; + let last, deferTimer; return function (...args) { - const context = this; const now = Date.now(); if (last && now < last + threshold) { clearTimeout(deferTimer); deferTimer = setTimeout(() => { last = now; - fn.apply(context, args); + fn.apply(this, args); }, threshold); } else { last = now; - fn.apply(context, args); + fn.apply(this, args); } }; } })(); +// Lazy Load (() => { function lazyLoad(options = {}) { - const { - selector = 'img[data-src]:not([src])', - loading = '', - failed = '', - rootMargin = '200px', - threshold = 0.01 - } = options; + const { selector = 'img[data-src]:not([src])', loading = '', failed = '', rootMargin = '200px', threshold = 0.01 } = options; const images = document.querySelectorAll(selector); @@ -123,17 +120,9 @@ const img = entry.target; observer.unobserve(img); - if (failed) { - const handleError = () => { - img.onerror = null; - img.src = failed; - img.setAttribute('data-failed', ''); - }; - img.onerror = handleError; - } - - img.removeAttribute('data-loading'); + img.onerror = failed ? () => { img.src = failed; img.setAttribute('data-failed', ''); } : null; img.src = img.getAttribute('data-src'); + img.removeAttribute('data-loading'); } }); }, { rootMargin, threshold }); @@ -154,32 +143,27 @@ rootMargin: '200px', threshold: 0.01 }; - if (document.readyState === 'loading') { - document.addEventListener("DOMContentLoaded", () => lazyLoad(lazyLoadOptions)); - } else { - lazyLoad(lazyLoadOptions); - } + + document.readyState === 'loading' + ? document.addEventListener("DOMContentLoaded", () => lazyLoad(lazyLoadOptions)) + : lazyLoad(lazyLoadOptions); })(); -// back to top +// Back to top (() => { - let isShow = false; - let lock = false; + let isShow = false, lock = false; const btn = document.querySelector('.back-to-top'); const handleScroll = () => { if (lock) return; - if (document.body.scrollTop >= 1000) { if (!isShow) { btn.classList.add('load'); isShow = true; } - } else { - if (isShow) { - btn.classList.remove('load'); - isShow = false; - } + } else if (isShow) { + btn.classList.remove('load'); + isShow = false; } }; @@ -206,3 +190,8 @@ window.addEventListener('scroll', handleScroll); btn.addEventListener('click', handleClick); })(); + +// Prevent gesture +(() => { + document.addEventListener('gesturestart', e => e.preventDefault()); +})(); diff --git a/assets/style.css b/assets/style.css deleted file mode 100644 index 010f353..0000000 --- a/assets/style.css +++ /dev/null @@ -1,113 +0,0 @@ -html { - scroll-padding: 50px 0; -} - -img[data-loading], -img[data-failed] { - width: 40px; -} - -@media screen and (min-width: 800px) { - body { - max-width: min(90%, 800px); - } -} - -details>summary { - list-style: none -} - -details>summary::-webkit-details-marker,details>summary::marker { - display: none -} - -summary:before { - border-bottom: 6px solid transparent; - border-left: 10px solid var(--b-txt); - border-top: 6px solid transparent; - content: ""; - display: inline-block; - height: 0; - margin-right: 10px; - position: relative; - transition: .2s; - width: 0 -} - -details[open] summary:before { - transform: rotate(90deg) -} - -h2, h3, h4, h5 { - margin-top: 1.5em; - margin-bottom: .6em; -} - -.back-to-top { - position: fixed; - z-index: 2; - right: -108px; - bottom: 0; - width: 108px; - height: 150px; - background: url('./img/back-to-top.png?v=1') no-repeat 0 0; /* artwork from https://www.pixiv.net/artworks/83996495 */ - background-size: 108px 450px; - opacity: 0.6; - transition: opacity 0.3s, right 0.8s; - cursor: pointer; -} -.back-to-top:hover { - background-position: 0 -150px; - opacity: 1; -} -.back-to-top.load { - right: 0; -} -.back-to-top.ani-leave { - background-position: 0 -150px; - animation: ani-leave 390ms ease-in-out; - animation-fill-mode: forwards; -} -.back-to-top.leaved { - pointer-events: none; - background: none; - transition: none; -} -.back-to-top.ending { - pointer-events: none; -} -.back-to-top.ending::after { - opacity: 1; - transition-delay: 0.35s; -} -.back-to-top::after { - content: ''; - position: fixed; - z-index: 2; - right: 0; - bottom: 0; - width: 108px; - height: 150px; - background: url('./img/back-to-top.png?v=1') no-repeat 0 0; - background-size: 108px 450px; - background-position: 0 -300px; - transition: opacity 0.3s; - opacity: 0; - pointer-events: none; -} - -@keyframes ani-leave { - 0% { - transform: translateX(0); - } - 100% { - transform: translateX(108px); - } -} - - -@media screen and (max-width: 900px) { - iframe { - display: none; - } -} \ No newline at end of file diff --git a/assets/style.less b/assets/style.less new file mode 100644 index 0000000..3f8249c --- /dev/null +++ b/assets/style.less @@ -0,0 +1,235 @@ +html { + scroll-padding: 50px 0; +} + +#main_title { + margin-top: 0.5em; +} + +#themes { + margin-top: 2em; + + & > p { + margin: 0; + } +} + +#more_theme h3 { + display: inline-block; + margin: 0; + cursor: pointer; +} + +#get { + margin-bottom: 1em; +} + +#code { + visibility: hidden; + display: inline-block; + margin-bottom: 1em; +} + +#result { + display: block; +} + +.github { + margin-top: 2em; +} + +code { + word-break: break-all; +} + +input[type="checkbox"][role="switch"] { + height: 0; + width: 0; + visibility: hidden; + + & + label { + cursor: pointer; + width: 3.6em; + height: 1.8em; + margin: 0; + background: grey; + display: block; + border-radius: 1.8em; + position: relative; + transition: 0.3s; + + &:after { + content: ""; + position: absolute; + top: 0.1em; + left: 0.1em; + width: 1.6em; + height: 1.6em; + background: #fff; + border-radius: 1.6em; + transition: 0.3s; + } + + span { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 12.5%; + font-size: 0.54em; + + &::before, + &::after { + display: block; + color: #fff; + font-weight: bold; + box-sizing: border-box; + } + + &::before { + content: "ON"; + } + + &::after { + content: "OFF"; + color: #ccc; + } + } + } + + &:checked + label { + background: var(--b-btn-bg); + + &:after { + left: calc(100% - 0.1em); + transform: translateX(-100%); + } + + &:active:after { + width: 2.34em; + } + } +} + +img[data-loading], +img[data-failed] { + width: 40px; +} + +details > summary { + list-style: none; + + &::-webkit-details-marker, + &::marker { + display: none; + } +} + +summary::before { + border-bottom: 6px solid transparent; + border-left: 10px solid var(--b-txt); + border-top: 6px solid transparent; + content: ""; + display: inline-block; + height: 0; + margin-right: 10px; + position: relative; + transition: 0.2s; + width: 0; +} + +details[open] summary::before { + transform: rotate(90deg); +} + +h2, +h3, +h4, +h5 { + margin: 1.5em 0 0.6em; +} + +.back-to-top { + position: fixed; + z-index: 2; + right: -108px; + bottom: 0; + width: 108px; + height: 150px; + background: url("./img/back-to-top.png?v=1") no-repeat 0 0; + background-size: 108px 450px; + opacity: 0.6; + transition: opacity 0.3s, right 0.8s; + cursor: pointer; + + &:hover { + background-position: 0 -150px; + opacity: 1; + } + + &::after { + content: ""; + position: fixed; + z-index: 2; + right: 0; + bottom: 0; + width: 108px; + height: 150px; + background: url("./img/back-to-top.png?v=1") no-repeat 0 0; + background-size: 108px 450px; + background-position: 0 -300px; + transition: opacity 0.3s; + opacity: 0; + pointer-events: none; + } + + &.load { + right: 0; + } + + &.ani-leave { + background-position: 0 -150px; + animation: ani-leave 390ms ease-in-out forwards; + + @keyframes ani-leave { + 0% { + transform: translateX(0); + } + + 100% { + transform: translateX(108px); + } + } + } + + &.leaved, + &.ending { + pointer-events: none; + } + + &.leaved { + background: none; + transition: none; + } + + &.ending::after { + opacity: 1; + transition-delay: 0.35s; + } +} + +@media screen and (min-width: 800px) { + body { + max-width: ~"min(90%, 800px)"; + } +} + +@media screen and (max-width: 900px) { + iframe { + display: none; + } +} diff --git a/views/index.pug b/views/index.pug index 76f8af8..d54e4e4 100644 --- a/views/index.pug +++ b/views/index.pug @@ -1,11 +1,12 @@ html head title='Moe Counter!' - meta(name='viewport', content='width=device-width, initial-scale=1') + meta(name='viewport', content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no') link(rel='icon', type='image/png', href='favicon.png') - link(rel='stylesheet', href='https://unpkg.com/normalize.css') - link(rel='stylesheet', href='https://unpkg.com/bamboo.css') - link(rel='stylesheet', href='style.css') + link(rel='stylesheet', href='https://cdn.jsdelivr.net/npm/normalize.css') + link(rel='stylesheet', href='https://cdn.jsdelivr.net/npm/bamboo.css') + link(rel='stylesheet/less', href='style.less') + script(less, src='https://cdn.jsdelivr.net/npm/less') script(async, src='https://www.googletagmanager.com/gtag/js?id=G-2RLWN5JXRL') script. @@ -26,7 +27,7 @@ html var __global_data = { site: "#{site}" }; body - h1#main_title(style='margin-top: 0.5em;') + h1#main_title i Moe Counter! h3 How to use @@ -46,10 +47,10 @@ html h5 e.g. img(src=`${site}/@index` alt="Moe Counter!") - details#themes(style='margin-top: 2em;') + details#themes summary#more_theme(onclick='_evt_push("click", "normal", "more_theme")') - h3(style='display: inline-block; margin: 0; cursor: pointer;') More theme✨ - p(style='margin: 0;') Just use the query parameters theme, like this: #{site}/@:name?theme=moebooru + h3 More theme✨ + p Just use the query parameters theme, like this: #{site}/@:name?theme=moebooru each theme in Object.keys(themeList) div.item(data-theme=theme) h5 #{theme} @@ -107,7 +108,9 @@ html td: code pixelated td Enable pixelated mode, Enum 0/1, default is code 1 - td: input#pixelated(type='checkbox', checked, style='margin: .5rem .75rem;') + td + input#pixelated(type='checkbox', role='switch', checked) + label(for='pixelated'): span tr td: code darkmode td Enable dark mode, Enum 0/1/auto, default is @@ -117,16 +120,16 @@ html option(value="1") yes option(value="0") no - button#get(style='margin-bottom: 1em;', onclick='_evt_push("click", "normal", "get_counter")') Generate + button#get(onclick='_evt_push("click", "normal", "get_counter")') Generate div - code#code(style='visibility: hidden; display: inline-block; margin-bottom: 1em;') - img#result(style='display: block;') + code#code + img#result - p(style='margin-top: 2em;') + p.github a(href='https://github.com/journey-ad/Moe-Counter', target='_blank', onclick='_evt_push("click", "normal", "go_github")') source code div.back-to-top - script(async, src='https://unpkg.com/party-js@2/bundle/party.min.js') + script(async, src='https://cdn.jsdelivr.net/npm/party-js@2/bundle/party.min.js') script(async, src='script.js')