mirror of
https://github.com/Sped0n/bridget.git
synced 2026-04-14 10:09:31 -07:00
feat: add new SCSS partials for collection and gallery components
- Added a new SCSS partial `_collection.scss` to define styles for the collection component. - Added a new SCSS partial `_gallery.scss` to define styles for the gallery component. The new partials contain styles for the collection and gallery components, including layout, positioning, and animations. --- feat: add support for gallery functionality in mobile - Created a new file `gallery.ts` to handle the gallery functionality in the mobile view. - Added functions `slideUp()` and `slideDown()` to handle the sliding animation of the gallery. - Initialized the gallery with the provided image data in the `initGallery()` function. - Added event listeners to update the active slide and index text when the slide is changed. - Created a helper function `changeSlide()` to change the active slide in the gallery. - Created a helper function `scrollToActive()` to scroll to the active image in the collection. - Created a helper function `updateIndexText()` to update the index text in the navigation. - Created a helper function `createGallery()` to dynamically create the gallery HTML structure. --- feat: add scrollable flag for mobile view - Created a new file `scroll.ts` to handle the scrollable flag for the mobile view. - Added a `scrollable` variable as a Watchable object to control the scrollability of the view. --- fix: change port variable case from lowercase `port` to uppercase `PORT` to improve semantics - Changed the variable name `port` to `PORT` in the `server.ts` file to improve code readability and semantics. - The variable `PORT` is now used to define the port number for the server to listen on.
This commit is contained in:
29
assets/scss/_partial/_collection.scss
Normal file
29
assets/scss/_partial/_collection.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
.collection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20vh;
|
||||
|
||||
padding-top: 50vh;
|
||||
margin-top: calc(var(--nav-height) * -1);
|
||||
|
||||
img {
|
||||
position: sticky;
|
||||
top: 50vh;
|
||||
|
||||
width: 60vw;
|
||||
height: 20vh;
|
||||
|
||||
object-fit: contain;
|
||||
|
||||
transform: translate3d(0, -50%, 0);
|
||||
align-self: center;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 20vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
56
assets/scss/_partial/_gallery.scss
Normal file
56
assets/scss/_partial/_gallery.scss
Normal file
@@ -0,0 +1,56 @@
|
||||
.gallery {
|
||||
pointer-events: all;
|
||||
|
||||
position: fixed;
|
||||
top: var(--nav-height);
|
||||
z-index: var(--z-nav-gallery);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: 100vw;
|
||||
height: calc(var(--window-height) - var(--nav-height));
|
||||
background: white;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
|
||||
.galleryInner {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
|
||||
.swiper-slide {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
height: var(--nav-height);
|
||||
padding: var(--space-standard);
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.curtain {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: var(--z-curtain);
|
||||
|
||||
width: 100vw;
|
||||
height: var(--window-height);
|
||||
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { container } from '../container'
|
||||
import { ImageJSON } from '../resources'
|
||||
import { setIndex } from '../state'
|
||||
import { getRandom, Watchable } from '../utils'
|
||||
import { slideUp } from './gallery'
|
||||
|
||||
/**
|
||||
* variables
|
||||
*/
|
||||
|
||||
export let imgs: HTMLImageElement[] = []
|
||||
export const mounted = new Watchable<boolean>(false)
|
||||
|
||||
/**
|
||||
* main functions
|
||||
*/
|
||||
|
||||
function handleClick(i: number): void {
|
||||
setIndex(i)
|
||||
slideUp()
|
||||
}
|
||||
|
||||
/**
|
||||
* init
|
||||
*/
|
||||
|
||||
export function initCollection(ijs: ImageJSON[]): void {
|
||||
createCollection(ijs)
|
||||
// get container
|
||||
const container = document
|
||||
.getElementsByClassName('collection')
|
||||
.item(0) as HTMLDivElement
|
||||
// add watcher
|
||||
mounted.addWatcher(() => {
|
||||
if (mounted.get()) {
|
||||
container.classList.remove('hidden')
|
||||
} else {
|
||||
container.classList.add('hidden')
|
||||
}
|
||||
})
|
||||
// get image elements
|
||||
imgs = Array.from(container.getElementsByTagName('img'))
|
||||
// add event listeners
|
||||
imgs.forEach((img, i) => {
|
||||
img.addEventListener('click', () => handleClick(i))
|
||||
img.addEventListener('keydown', () => handleClick(i))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* helper
|
||||
*/
|
||||
|
||||
function createCollection(ijs: ImageJSON[]): void {
|
||||
// create container for images
|
||||
const collection: HTMLDivElement = document.createElement('div')
|
||||
collection.className = 'collection'
|
||||
// append images to container
|
||||
for (let [i, ij] of ijs.entries()) {
|
||||
// random x and y
|
||||
const x = i !== 0 ? getRandom(-25, 25) : 0
|
||||
const y = i !== 0 ? getRandom(-30, 30) : 0
|
||||
// element
|
||||
const e = document.createElement('img')
|
||||
e.src = ij.url
|
||||
e.height = ij.imgH
|
||||
e.width = ij.imgW
|
||||
e.alt = 'image'
|
||||
e.style.transform = `translate3d(${x}%, ${y - 50}%, 0)`
|
||||
collection.append(e)
|
||||
}
|
||||
container.append(collection)
|
||||
}
|
||||
|
||||
198
assets/ts/mobile/gallery.ts
Normal file
198
assets/ts/mobile/gallery.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import { Power3, gsap } from 'gsap'
|
||||
import Swiper from 'swiper'
|
||||
import { container } from '../container'
|
||||
import { ImageJSON } from '../resources'
|
||||
import { setIndex, state } from '../state'
|
||||
import { Watchable, expand } from '../utils'
|
||||
import { imgs, mounted } from './collection'
|
||||
import { scrollable } from './scroll'
|
||||
|
||||
/**
|
||||
* variables
|
||||
*/
|
||||
|
||||
let swiperNode: HTMLDivElement
|
||||
let gallery: HTMLDivElement
|
||||
let curtain: HTMLDivElement
|
||||
let swiper: Swiper
|
||||
const isAnimating = new Watchable<boolean>(false)
|
||||
let lastIndex = -1
|
||||
let indexDispNums: HTMLSpanElement[] = []
|
||||
|
||||
/**
|
||||
* main functions
|
||||
*/
|
||||
|
||||
export function slideUp(): void {
|
||||
if (isAnimating.get()) return
|
||||
isAnimating.set(true)
|
||||
|
||||
gsap.to(curtain, {
|
||||
opacity: 1,
|
||||
duration: 1
|
||||
})
|
||||
|
||||
gsap.to(gallery, {
|
||||
y: 0,
|
||||
ease: Power3.easeInOut,
|
||||
duration: 1,
|
||||
delay: 0.4
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
scrollable.set(false)
|
||||
isAnimating.set(false)
|
||||
}, 1200)
|
||||
}
|
||||
|
||||
function slideDown(): void {
|
||||
scrollable.set(true)
|
||||
scrollToActive()
|
||||
|
||||
gsap.to(gallery, {
|
||||
y: '100%',
|
||||
ease: Power3.easeInOut,
|
||||
duration: 1
|
||||
})
|
||||
|
||||
gsap.to(curtain, {
|
||||
opacity: 0,
|
||||
duration: 1.2,
|
||||
delay: 0.4
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* init
|
||||
*/
|
||||
|
||||
export function initGallery(ijs: ImageJSON[]): void {
|
||||
// create gallery
|
||||
createGallery(ijs)
|
||||
// get elements
|
||||
indexDispNums = Array.from(
|
||||
document.getElementsByClassName('nav').item(0)!.getElementsByClassName('num')
|
||||
) as HTMLSpanElement[]
|
||||
swiperNode = document.getElementsByClassName('galleryInner').item(0) as HTMLDivElement
|
||||
gallery = document.getElementsByClassName('gallery').item(0) as HTMLDivElement
|
||||
curtain = document.getElementsByClassName('curtain').item(0) as HTMLDivElement
|
||||
// state watcher
|
||||
state.addWatcher(() => {
|
||||
const s = state.get()
|
||||
// change slide only when index is changed
|
||||
if (s.index === lastIndex) return
|
||||
changeSlide(s.index)
|
||||
updateIndexText()
|
||||
lastIndex = s.index
|
||||
})
|
||||
// mounted watcher
|
||||
mounted.addWatcher(() => {
|
||||
if (!mounted.get()) return
|
||||
scrollable.set(true)
|
||||
swiper = new Swiper(swiperNode, { spaceBetween: 20 })
|
||||
swiper.on('slideChange', ({ realIndex }) => {
|
||||
setIndex(realIndex)
|
||||
})
|
||||
})
|
||||
|
||||
// mounted
|
||||
mounted.set(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* helper
|
||||
*/
|
||||
|
||||
function changeSlide(slide: number): void {
|
||||
console.log(slide)
|
||||
swiper?.slideTo(slide, 0)
|
||||
}
|
||||
|
||||
function scrollToActive(): void {
|
||||
imgs[state.get().index].scrollIntoView({
|
||||
block: 'center',
|
||||
behavior: 'auto'
|
||||
})
|
||||
}
|
||||
|
||||
function updateIndexText(): void {
|
||||
const indexValue: string = expand(state.get().index + 1)
|
||||
const indexLength: string = expand(state.get().length)
|
||||
indexDispNums.forEach((e: HTMLSpanElement, i: number) => {
|
||||
if (i < 4) {
|
||||
e.innerText = indexValue[i]
|
||||
} else {
|
||||
e.innerText = indexLength[i - 4]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createGallery(ijs: ImageJSON[]): void {
|
||||
/**
|
||||
* gallery
|
||||
* |- galleryInner
|
||||
* |- swiper-wrapper
|
||||
* |- swiper-slide
|
||||
* |- img
|
||||
* |- swiper-slide
|
||||
* |- img
|
||||
* |- ...
|
||||
* |- nav
|
||||
* |- index
|
||||
* |- close
|
||||
*/
|
||||
// swiper wrapper
|
||||
const _swiperWrapper = document.createElement('div')
|
||||
_swiperWrapper.className = 'swiper-wrapper'
|
||||
// swiper slide
|
||||
for (let ij of ijs) {
|
||||
const _swiperSlide = document.createElement('div')
|
||||
_swiperSlide.className = 'swiper-slide'
|
||||
// img
|
||||
const e = document.createElement('img')
|
||||
e.src = ij.url
|
||||
e.alt = 'image'
|
||||
// append
|
||||
_swiperSlide.append(e)
|
||||
_swiperWrapper.append(_swiperSlide)
|
||||
}
|
||||
// swiper node
|
||||
const _swiperNode = document.createElement('div')
|
||||
_swiperNode.className = 'galleryInner'
|
||||
_swiperNode.append(_swiperWrapper)
|
||||
// index
|
||||
const _index = document.createElement('div')
|
||||
_index.insertAdjacentHTML(
|
||||
'afterbegin',
|
||||
`<span class="num"></span><span class="num"></span><span class="num"></span><span class="num"></span>
|
||||
<span>/</span>
|
||||
<span class="num"></span><span class="num"></span><span class="num"></span><span class="num"></span>`
|
||||
)
|
||||
// close
|
||||
const _close = document.createElement('div')
|
||||
_close.innerText = 'Close'
|
||||
_close.addEventListener('click', () => slideDown())
|
||||
_close.addEventListener('keydown', () => slideDown())
|
||||
// nav
|
||||
const _navDiv = document.createElement('div')
|
||||
_navDiv.className = 'nav'
|
||||
_navDiv.append(_index, _close)
|
||||
// gallery
|
||||
const _gallery = document.createElement('div')
|
||||
_gallery.className = 'gallery'
|
||||
_gallery.append(_swiperNode)
|
||||
_gallery.append(_navDiv)
|
||||
|
||||
/**
|
||||
* curtain
|
||||
*/
|
||||
const _curtain = document.createElement('div')
|
||||
_curtain.className = 'curtain'
|
||||
|
||||
/**
|
||||
* container
|
||||
* |- gallery
|
||||
* |- curtain
|
||||
*/
|
||||
container.append(_gallery, _curtain)
|
||||
}
|
||||
3
assets/ts/mobile/scroll.ts
Normal file
3
assets/ts/mobile/scroll.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Watchable } from '../utils'
|
||||
|
||||
export const scrollable = new Watchable<boolean>(true)
|
||||
@@ -1,9 +1,13 @@
|
||||
{{- $fingerprint := .Scratch.Get "fingerprint" | default "" -}}
|
||||
{{- $style := dict "Source" "css/style.scss" "Fingerprint" $fingerprint -}}
|
||||
{{- $style := dict "Source" "scss/style.scss" "Fingerprint" $fingerprint -}}
|
||||
{{- $options := dict "targetPath" "css/style.min.css" "enableSourceMap" true -}}
|
||||
{{- $style = dict "Context" . "ToCSS" $options | merge $style -}}
|
||||
{{- partial "plugin/style.html" $style -}}
|
||||
{{- $esBuildOpts := dict "minify" hugo.IsProduction -}}
|
||||
|
||||
{{- $script := resources.Get "ts/main.ts" | js.Build $esBuildOpts -}}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"
|
||||
/>
|
||||
<script type="text/javascript" src="{{ $script.RelPermalink }}" defer></script>
|
||||
|
||||
Reference in New Issue
Block a user