style: judge移动端
This commit is contained in:
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
@@ -32,7 +32,7 @@ let flexible = (designWidth, maxWidth, minWidth) => {
|
|||||||
} else {
|
} else {
|
||||||
designWidth = 750
|
designWidth = 750
|
||||||
}
|
}
|
||||||
console.log(width, designWidth)
|
// console.log(width, designWidth)
|
||||||
|
|
||||||
// var rem = width * 10 / designWidth;
|
// var rem = width * 10 / designWidth;
|
||||||
var rem = Math.round((width * 10) / designWidth)
|
var rem = Math.round((width * 10) / designWidth)
|
||||||
|
|||||||
@@ -1,34 +1,54 @@
|
|||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted, onActivated, onDeactivated } from 'vue'
|
||||||
|
|
||||||
export const useIsMobile = () => {
|
export const useIsMobile = () => {
|
||||||
const isMobile = ref(false)
|
const isMobile = ref(false)
|
||||||
|
|
||||||
|
let resizeTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
const checkDevice = () => {
|
const checkDevice = () => {
|
||||||
// 1. 现代 Client Hints API(Chrome/Edge 最准)
|
// 使用防抖避免频繁触发
|
||||||
if (navigator.userAgentData?.mobile !== undefined) {
|
if (resizeTimer) {
|
||||||
isMobile.value = navigator.userAgentData.mobile
|
clearTimeout(resizeTimer)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
resizeTimer = setTimeout(() => {
|
||||||
|
// 1. 现代 Client Hints API(Chrome/Edge 最准)
|
||||||
|
// if (navigator.userAgentData?.mobile !== undefined) {
|
||||||
|
// isMobile.value = navigator.userAgentData.mobile
|
||||||
|
// console.log('使用 userAgentData:', isMobile.value)
|
||||||
|
// }
|
||||||
|
// 2. 综合判断(兼容所有浏览器)
|
||||||
|
const ua = navigator.userAgent.toLowerCase()
|
||||||
|
const mobileRegex = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i
|
||||||
|
|
||||||
// 2. 综合判断(兼容所有浏览器)
|
const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 1
|
||||||
const ua = navigator.userAgent.toLowerCase()
|
const smallScreen = window.innerWidth <= 1200 // 你可以改成 1024 等
|
||||||
const mobileRegex = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i
|
|
||||||
|
|
||||||
const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 1
|
const uaCheck = mobileRegex.test(ua)
|
||||||
const smallScreen = window.innerWidth <= 768 // 你可以改成 1024 等
|
isMobile.value = uaCheck || smallScreen
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
|
||||||
isMobile.value = mobileRegex.test(ua) || (hasTouch && smallScreen)
|
const handleResize = () => {
|
||||||
|
checkDevice()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkDevice()
|
checkDevice()
|
||||||
window.addEventListener('resize', checkDevice)
|
window.addEventListener('resize', handleResize)
|
||||||
window.addEventListener('orientationchange', checkDevice) // 手机旋转必备
|
window.addEventListener('orientationchange', checkDevice) // 手机旋转必备
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', checkDevice)
|
window.removeEventListener('resize', handleResize)
|
||||||
window.removeEventListener('orientationchange', checkDevice)
|
window.removeEventListener('orientationchange', checkDevice)
|
||||||
|
if (resizeTimer) {
|
||||||
|
clearTimeout(resizeTimer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理 keep-alive 缓存的组件重新激活场景
|
||||||
|
onActivated(() => {
|
||||||
|
checkDevice()
|
||||||
})
|
})
|
||||||
|
|
||||||
return { isMobile }
|
return { isMobile }
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="judges-container flex flex-col align-center">
|
<div class="judges-container flex flex-col align-center" :class="{ mobile: isMobile }">
|
||||||
<div class="title" ref="judgesTitleRef">{{ $t('AwardsPage.panelOfJudges') }}</div>
|
<div class="title" ref="judgesTitleRef">{{ $t('AwardsPage.panelOfJudges') }}</div>
|
||||||
<!-- <img src="@/assets/images/award/bloom_logo.png" class="logo" /> -->
|
<!-- <img src="@/assets/images/award/bloom_logo.png" class="logo" /> -->
|
||||||
<div class="sub-title" ref="judgesSubTitleRef">{{ $t('AwardsPage.expertise') }}</div>
|
<div class="sub-title" ref="judgesSubTitleRef">{{ $t('AwardsPage.expertise') }}</div>
|
||||||
<div class="judgement-list" ref="judgementListRef">
|
<div class="judgement-list" ref="judgementListRef">
|
||||||
<div
|
<div
|
||||||
class="judgement-item flex flex-col align-center"
|
class="judgement-item flex flex-col align-center"
|
||||||
v-for="item in judgements"
|
v-for="item in judgements"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
>
|
>
|
||||||
<img :src="item.picture" class="picture" />
|
<img :src="item.picture" class="picture" />
|
||||||
<div class="name">{{ $t(item.name) }}</div>
|
<div class="name">{{ $t(item.name) }}</div>
|
||||||
<div class="desc">{{ $t(item.desc) }}</div>
|
<div class="desc">{{ $t(item.desc) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onBeforeUnmount, onMounted, nextTick, ref } from 'vue'
|
import { onBeforeUnmount, onMounted, nextTick, ref, inject } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { gsap } from 'gsap'
|
import { gsap } from 'gsap'
|
||||||
import jae from '@/assets/images/award/jae.png'
|
import jae from '@/assets/images/award/jae.png'
|
||||||
@@ -29,38 +29,38 @@ import tim from '@/assets/images/award/tim.png'
|
|||||||
import desmond from '@/assets/images/award/desmond.png'
|
import desmond from '@/assets/images/award/desmond.png'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const isMobile = inject<boolean>('isMobile')
|
||||||
const judgements = [
|
const judgements = [
|
||||||
{
|
{
|
||||||
picture: jae,
|
picture: jae,
|
||||||
name: 'Jae Hyuk Lim',
|
name: 'Jae Hyuk Lim',
|
||||||
desc: 'AwardsPage.judgesHat.jae'
|
desc: 'AwardsPage.judgesHat.jae'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
picture: diego,
|
picture: diego,
|
||||||
name: 'Diego Dultzin Lacoste',
|
name: 'Diego Dultzin Lacoste',
|
||||||
desc: 'AwardsPage.judgesHat.diego'
|
desc: 'AwardsPage.judgesHat.diego'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
picture: gregory,
|
picture: gregory,
|
||||||
name: 'Gregory de la Hogue Moran',
|
name: 'Gregory de la Hogue Moran',
|
||||||
desc: 'AwardsPage.judgesHat.gregory'
|
desc: 'AwardsPage.judgesHat.gregory'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
picture: vincenzo,
|
picture: vincenzo,
|
||||||
name: 'Vincenzo La Torre',
|
name: 'Vincenzo La Torre',
|
||||||
desc: 'AwardsPage.judgesHat.vincenzo'
|
desc: 'AwardsPage.judgesHat.vincenzo'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
picture: tim,
|
picture: tim,
|
||||||
name: 'Tim Lim',
|
name: 'Tim Lim',
|
||||||
desc: 'AwardsPage.judgesHat.tim'
|
desc: 'AwardsPage.judgesHat.tim'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
picture: desmond,
|
picture: desmond,
|
||||||
name: 'Desmond Lim',
|
name: 'Desmond Lim',
|
||||||
desc: 'AwardsPage.judgesHat.desmond'
|
desc: 'AwardsPage.judgesHat.desmond'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const judgesTitleRef = ref<HTMLElement | null>(null)
|
const judgesTitleRef = ref<HTMLElement | null>(null)
|
||||||
@@ -70,165 +70,196 @@ const hasPlayedJudgementAnim = ref(false)
|
|||||||
let judgementObserver: IntersectionObserver | null = null
|
let judgementObserver: IntersectionObserver | null = null
|
||||||
|
|
||||||
const setupJudgementInitialState = () => {
|
const setupJudgementInitialState = () => {
|
||||||
const titleEls = [judgesTitleRef.value, judgesSubTitleRef.value].filter(
|
const titleEls = [judgesTitleRef.value, judgesSubTitleRef.value].filter(Boolean) as HTMLElement[]
|
||||||
Boolean
|
if (titleEls.length) {
|
||||||
) as HTMLElement[]
|
gsap.set(titleEls, {
|
||||||
if (titleEls.length) {
|
opacity: 0,
|
||||||
gsap.set(titleEls, {
|
scale: 0,
|
||||||
opacity: 0,
|
transformOrigin: '50% 50%'
|
||||||
scale: 0,
|
})
|
||||||
transformOrigin: '50% 50%'
|
}
|
||||||
})
|
const items = judgementListRef.value?.querySelectorAll<HTMLElement>('.judgement-item')
|
||||||
}
|
if (items?.length) {
|
||||||
const items =
|
gsap.set(items, {
|
||||||
judgementListRef.value?.querySelectorAll<HTMLElement>('.judgement-item')
|
opacity: 0,
|
||||||
if (items?.length) {
|
clipPath: 'inset(0 0 100% 0)'
|
||||||
gsap.set(items, {
|
})
|
||||||
opacity: 0,
|
}
|
||||||
clipPath: 'inset(0 0 100% 0)'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const playJudgementAnimation = () => {
|
const playJudgementAnimation = () => {
|
||||||
if (hasPlayedJudgementAnim.value) return
|
if (hasPlayedJudgementAnim.value) return
|
||||||
const titleEls = [judgesTitleRef.value, judgesSubTitleRef.value].filter(
|
const titleEls = [judgesTitleRef.value, judgesSubTitleRef.value].filter(Boolean) as HTMLElement[]
|
||||||
Boolean
|
const listEl = judgementListRef.value
|
||||||
) as HTMLElement[]
|
if (!titleEls.length || !listEl) return
|
||||||
const listEl = judgementListRef.value
|
|
||||||
if (!titleEls.length || !listEl) return
|
|
||||||
|
|
||||||
const items = Array.from(
|
const items = Array.from(listEl.querySelectorAll<HTMLElement>('.judgement-item'))
|
||||||
listEl.querySelectorAll<HTMLElement>('.judgement-item')
|
const tl = gsap.timeline({ defaults: { ease: 'power2.out' } })
|
||||||
)
|
|
||||||
const tl = gsap.timeline({ defaults: { ease: 'power2.out' } })
|
|
||||||
|
|
||||||
tl.to(titleEls, {
|
tl.to(titleEls, {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
scale: 1,
|
scale: 1,
|
||||||
duration: 0.4,
|
duration: 0.4,
|
||||||
ease: 'back.out(1.6)',
|
ease: 'back.out(1.6)',
|
||||||
stagger: 0.1
|
stagger: 0.1
|
||||||
})
|
})
|
||||||
if (items.length) {
|
if (items.length) {
|
||||||
const firstRow = items.slice(0, 3)
|
const firstRow = items.slice(0, 3)
|
||||||
const secondRow = items.slice(3)
|
const secondRow = items.slice(3)
|
||||||
|
|
||||||
if (firstRow.length) {
|
if (firstRow.length) {
|
||||||
tl.to(
|
tl.to(
|
||||||
firstRow,
|
firstRow,
|
||||||
{
|
{
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
clipPath: 'inset(0% 0% 0% 0%)',
|
clipPath: 'inset(0% 0% 0% 0%)',
|
||||||
duration: 0.45,
|
duration: 0.45,
|
||||||
stagger: 0.05
|
stagger: 0.05
|
||||||
},
|
},
|
||||||
'-=0.2'
|
'-=0.2'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (secondRow.length) {
|
if (secondRow.length) {
|
||||||
tl.to(
|
tl.to(
|
||||||
secondRow,
|
secondRow,
|
||||||
{
|
{
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
clipPath: 'inset(0% 0% 0% 0%)',
|
clipPath: 'inset(0% 0% 0% 0%)',
|
||||||
duration: 0.45,
|
duration: 0.45,
|
||||||
stagger: 0.05
|
stagger: 0.05
|
||||||
},
|
},
|
||||||
'+=0.1'
|
'+=0.1'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPlayedJudgementAnim.value = true
|
hasPlayedJudgementAnim.value = true
|
||||||
judgementObserver?.disconnect()
|
judgementObserver?.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setupJudgementInitialState()
|
setupJudgementInitialState()
|
||||||
if ('IntersectionObserver' in window) {
|
if ('IntersectionObserver' in window) {
|
||||||
judgementObserver = new IntersectionObserver(
|
judgementObserver = new IntersectionObserver(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
if (entry.isIntersecting) {
|
if (entry.isIntersecting) {
|
||||||
playJudgementAnimation()
|
playJudgementAnimation()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ threshold: 0.3 }
|
{ threshold: 0.3 }
|
||||||
)
|
)
|
||||||
if (judgementListRef.value) {
|
if (judgementListRef.value) {
|
||||||
judgementObserver.observe(judgementListRef.value)
|
judgementObserver.observe(judgementListRef.value)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback: play immediately if IntersectionObserver unsupported
|
// Fallback: play immediately if IntersectionObserver unsupported
|
||||||
playJudgementAnimation()
|
playJudgementAnimation()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
judgementObserver?.disconnect()
|
judgementObserver?.disconnect()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.judges-container {
|
.judges-container {
|
||||||
height: 147.4rem;
|
height: 147.4rem;
|
||||||
background: url('@/assets/images/award/judges_bg.png') no-repeat;
|
background: url('@/assets/images/award/judges_bg.png') no-repeat;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
padding-top: 12.8rem;
|
padding-top: 12.8rem;
|
||||||
.title {
|
.title {
|
||||||
color: #232323;
|
color: #232323;
|
||||||
font-family: 'PoppinsBold';
|
font-family: 'PoppinsBold';
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 4rem;
|
font-size: 4rem;
|
||||||
margin-bottom: 2.4rem;
|
margin-bottom: 2.4rem;
|
||||||
}
|
}
|
||||||
.logo {
|
.logo {
|
||||||
margin: 2.4rem 0 2.2rem;
|
margin: 2.4rem 0 2.2rem;
|
||||||
}
|
}
|
||||||
.sub-title {
|
.sub-title {
|
||||||
color: #b10000;
|
color: #b10000;
|
||||||
font-family: 'Arial';
|
font-family: 'Arial';
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
margin-bottom: 12rem;
|
margin-bottom: 12rem;
|
||||||
}
|
}
|
||||||
.judgement-list {
|
.judgement-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
column-gap: 23.22rem;
|
column-gap: 23.22rem;
|
||||||
row-gap: 8rem;
|
row-gap: 8rem;
|
||||||
padding: 0 25rem 0 26.6rem;
|
padding: 0 25rem 0 26.6rem;
|
||||||
div{
|
div {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.judgement-item {
|
.judgement-item {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.picture {
|
.picture {
|
||||||
width: 20.2rem;
|
width: 20.2rem;
|
||||||
height: 26rem;
|
height: 26rem;
|
||||||
border-radius: 0.8rem;
|
border-radius: 0.8rem;
|
||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
margin: 3rem 0 2.4rem;
|
margin: 3rem 0 2.4rem;
|
||||||
color: #232323;
|
color: #232323;
|
||||||
font-family: 'PoppinsBold';
|
font-family: 'PoppinsBold';
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 2.4rem;
|
font-size: 2.4rem;
|
||||||
}
|
}
|
||||||
.desc {
|
.desc {
|
||||||
color: #585858;
|
color: #585858;
|
||||||
font-family: 'Arial';
|
font-family: 'Arial';
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.mobile {
|
||||||
|
height: 139.6rem;
|
||||||
|
background: url('@/assets/images/mobile_version_background/judge_bg.png') no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
padding-top: 6rem;
|
||||||
|
.title {
|
||||||
|
font-size: 3.2rem;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
}
|
||||||
|
.sub-title {
|
||||||
|
font-family: 'PoppinsMedium';
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 2.4rem;
|
||||||
|
margin-bottom: 5.8rem;
|
||||||
|
}
|
||||||
|
.judgement-list {
|
||||||
|
padding: 0 8.6rem;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
column-gap: 6.8rem;
|
||||||
|
row-gap: 6rem;
|
||||||
|
// row-gap: 4rem;
|
||||||
|
// padding: 0 1.6rem;
|
||||||
|
.judgement-item {
|
||||||
|
.picture {
|
||||||
|
width: 15.6rem;
|
||||||
|
height: 20rem;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
font-size: 2.4rem;
|
||||||
|
margin: 1.2rem 0;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
font-family: 'Instrument';
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="award-page" :class="{ 'is-zh': isZh }">
|
<div class="award-page" :class="{ 'is-zh': isZh }">
|
||||||
<div class="banner">
|
<div class="banner" :class="{ 'mobile': isMobile }">
|
||||||
<video
|
<video
|
||||||
:src="bannerUrl"
|
:src="bannerUrl"
|
||||||
autoplay
|
autoplay
|
||||||
|
|||||||
Reference in New Issue
Block a user