유저자료실
PWA 앱 설치 이후 플로팅 버튼에 대한 수정 코드
- 토시아 14일 전 2025.10.21 21:22 기타
-
18
0
<?php
if (!defined('_GNUBOARD_')) exit;
include_once("./_common.php");
?>
<style type="text/css">
/* 페이지 하단 여백 - 탭바 높이만큼 */
body.pwa-mobile {
padding-bottom: 60px;
}
/* 하단 고정 탭바 스타일 */
.floating-tabbar {
display: none;
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-top: 1px solid rgba(255, 255, 255, 0.2);
z-index: 1000;
padding: 0 10px;
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.2);
transition: background 0.3s ease;
}
/* 밝은 배경용 스타일 */
.floating-tabbar.light-mode {
background: rgba(255, 255, 255, 0.95);
border-top: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.1);
}
.floating-tabbar-container {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
width: 100%;
}
/* 히스토리 네비게이션 */
.floating-tabbar-history {
display: flex;
align-items: center;
gap: 8px;
margin-right: 3px;
}
.floating-tabbar-history-btn {
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 35px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.floating-tabbar-history-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.floating-tabbar-history-btn::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.2);
border-radius: 8px;
opacity: 0;
transition: opacity 0.3s ease;
}
.floating-tabbar.light-mode .floating-tabbar-history-btn::before {
background: rgba(0, 0, 0, 0.1);
}
.floating-tabbar-history-btn:not(:disabled):active::before {
opacity: 1;
}
.floating-tabbar-history-btn:not(:disabled):active {
transform: scale(0.9);
}
.floating-tabbar-history-icon {
width: 24px;
height: 24px;
stroke: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
}
.floating-tabbar.light-mode .floating-tabbar-history-icon {
stroke: rgba(0, 0, 0, 0.8);
}
.floating-tabbar-menu {
display: flex;
align-items: center;
flex: 1;
}
.floating-tabbar-menu > div {
flex: 1;
}
.floating-tabbar-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
cursor: pointer;
transition: all 0.3s ease;
padding: 8px 12px;
border-radius: 12px;
user-select: none;
position: relative;
overflow: hidden;
flex: 1;
text-decoration: none;
}
.floating-tabbar-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
opacity: 0;
transition: opacity 0.3s ease;
}
.floating-tabbar.light-mode .floating-tabbar-item::before {
background: rgba(0, 0, 0, 0.1);
}
.floating-tabbar-item:active::before {
opacity: 1;
}
.floating-tabbar-icon {
width: 20px;
height: 20px;
stroke: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
}
.floating-tabbar.light-mode .floating-tabbar-icon {
stroke: rgba(0, 0, 0, 0.8);
}
.floating-tabbar-item:active .floating-tabbar-icon {
transform: scale(0.9);
}
.floating-tabbar-label {
font-size: 10px;
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
transition: color 0.3s ease;
}
.floating-tabbar.light-mode .floating-tabbar-label {
color: rgba(0, 0, 0, 0.7);
}
.floating-tabbar-refresh {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
background: rgba(255, 255, 255, 0.3);
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.floating-tabbar.light-mode .floating-tabbar-refresh {
background: rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.2);
}
.floating-tabbar-refresh::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
opacity: 0;
transition: opacity 0.3s ease;
}
.floating-tabbar.light-mode .floating-tabbar-refresh::before {
background: rgba(0, 0, 0, 0.1);
}
.floating-tabbar-refresh:active::before {
opacity: 1;
}
.floating-tabbar-refresh:active {
transform: scale(0.95);
}
.floating-tabbar-refresh-icon {
width: 20px;
height: 20px;
stroke: #fff;
stroke-width: 2;
transition: transform 0.6s ease;
}
.floating-tabbar.light-mode .floating-tabbar-refresh-icon {
stroke: #000;
}
.floating-tabbar-refresh.spinning .floating-tabbar-refresh-icon {
transform: rotate(360deg);
}
/* PWA 모바일에서만 표시 */
.pwa-mobile .floating-tabbar {
display: block;
}
/* 매우 작은 화면에서 간격 조정 */
@media (max-width: 400px) {
.floating-tabbar {
padding: 0 8px;
}
.floating-tabbar-item {
padding: 6px 6px;
}
.floating-tabbar-refresh {
width: 40px;
height: 40px;
}
.floating-tabbar-history {
margin-right: 4px;
}
.floating-tabbar-history-btn {
width: 24px;
height: 24px;
}
.floating-tabbar-history-icon {
width: 12px;
height: 12px;
}
}
/* 안전 영역 대응 (iPhone X 이후) */
@supports (bottom: env(safe-area-inset-bottom)) {
.floating-tabbar {
padding-bottom: env(safe-area-inset-bottom);
height: calc(60px + env(safe-area-inset-bottom));
}
body.pwa-mobile {
padding-bottom: calc(60px + env(safe-area-inset-bottom));
}
}
</style>
<!-- 모바일 하단 고정 탭바 -->
<div class="floating-tabbar">
<div class="floating-tabbar-container">
<!-- 히스토리 네비게이션 -->
<div class="floating-tabbar-history">
<button class="floating-tabbar-history-btn" id="historyBack" onclick="goBack()" disabled>
<svg class="floating-tabbar-history-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15,18 9,12 15,6"/>
</svg>
</button>
<button class="floating-tabbar-history-btn" id="historyForward" onclick="goForward()" disabled>
<svg class="floating-tabbar-history-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9,18 15,12 9,6"/>
</svg>
</button>
</div>
<div class="floating-tabbar-menu">
<?php if (isset($is_member) ? $is_member : '') { ?>
<!-- 회원용 탭바 -->
<div><a href="<?php echo G5_URL ?>" class="floating-tabbar-item">
<svg class="floating-tabbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
<polyline points="9,22 9,12 15,12 15,22"/>
</svg>
<span class="floating-tabbar-label">홈</span>
</a></div>
<div><a href="<?php echo G5_URL ?>/dashboard" class="floating-tabbar-item">
<svg class="floating-tabbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7"/>
<rect x="14" y="3" width="7" height="7"/>
<rect x="14" y="14" width="7" height="7"/>
<rect x="3" y="14" width="7" height="7"/>
</svg>
<span class="floating-tabbar-label">대시보드</span>
</a></div>
<div><a href="javascript:void(0);" onclick="toggleFloatingNotification()" class="floating-tabbar-item">
<svg class="floating-tabbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
<polyline points="22,6 12,13 2,6"/>
</svg>
<span class="floating-tabbar-label">알림</span>
</a></div>
<div><a href="<?php echo G5_URL ?>/rb/home.php?mb_id=<?php echo $member['mb_id'] ?>" class="floating-tabbar-item">
<svg class="floating-tabbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</svg>
<span class="floating-tabbar-label">마이페이지</span>
</a></div>
<?php } else { ?>
<!-- 비회원용 탭바 -->
<div><a href="<?php echo G5_URL ?>" class="floating-tabbar-item">
<svg class="floating-tabbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
<polyline points="9,22 9,12 15,12 15,22"/>
</svg>
<span class="floating-tabbar-label">홈</span>
</a></div>
<div><a href="<?php echo G5_BBS_URL ?>/board.php?bo_table=free" class="floating-tabbar-item">
<svg class="floating-tabbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 3h18v18H3zM9 9h6m-6 4h6m-6 4h4"/>
<line x1="9" y1="9" x2="15" y2="9"/>
<line x1="9" y1="13" x2="15" y2="13"/>
<line x1="9" y1="17" x2="13" y2="17"/>
</svg>
<span class="floating-tabbar-label">게시판</span>
</a></div>
<div><a href="<?php echo G5_BBS_URL ?>/board.php?bo_table=notice" class="floating-tabbar-item">
<svg class="floating-tabbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14,2 14,8 20,8"/>
<line x1="16" y1="13" x2="8" y2="13"/>
<line x1="16" y1="17" x2="8" y2="17"/>
<polyline points="10,9 9,9 8,9"/>
</svg>
<span class="floating-tabbar-label">공지</span>
</a></div>
<div><a href="<?php echo G5_BBS_URL ?>/login.php" class="floating-tabbar-item">
<svg class="floating-tabbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/>
<polyline points="10,17 15,12 10,7"/>
<line x1="15" y1="12" x2="3" y2="12"/>
</svg>
<span class="floating-tabbar-label">로그인</span>
</a></div>
<?php } ?>
</div>
<button class="floating-tabbar-refresh" onclick="floatingTabbarRefresh()">
<svg class="floating-tabbar-refresh-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
</svg>
</button>
</div>
</div>
<script>
// 히스토리 네비게이션 함수들
function updateHistoryButtons() {
const backBtn = document.getElementById('historyBack');
const forwardBtn = document.getElementById('historyForward');
const currentIndex = parseInt(sessionStorage.getItem('historyIndex') || '0');
const maxIndex = parseInt(sessionStorage.getItem('maxHistoryIndex') || '0');
backBtn.disabled = currentIndex <= 0;
forwardBtn.disabled = currentIndex >= maxIndex;
}
function goBack() {
if (window.history.length > 1) {
const currentIndex = parseInt(sessionStorage.getItem('historyIndex') || '0');
sessionStorage.setItem('historyIndex', Math.max(0, currentIndex - 1).toString());
window.history.back();
}
}
function goForward() {
const currentIndex = parseInt(sessionStorage.getItem('historyIndex') || '0');
const maxIndex = parseInt(sessionStorage.getItem('maxHistoryIndex') || '0');
if (currentIndex < maxIndex) {
sessionStorage.setItem('historyIndex', (currentIndex + 1).toString());
window.history.forward();
}
}
// 페이지 로드 시 히스토리 인덱스 업데이트
function trackPageNavigation() {
const currentIndex = parseInt(sessionStorage.getItem('historyIndex') || '0');
if (performance.navigation.type === 0) {
const newIndex = currentIndex + 1;
sessionStorage.setItem('historyIndex', newIndex.toString());
sessionStorage.setItem('maxHistoryIndex', newIndex.toString());
}
updateHistoryButtons();
}
// 배경 밝기 감지 및 테마 자동 변경
function detectFloatingTabbarBrightness() {
const tabbar = document.querySelector('.floating-tabbar');
if (!tabbar) return;
const footer = document.querySelector('footer');
if (footer) {
const footerRect = footer.getBoundingClientRect();
const windowHeight = window.innerHeight;
// 푸터가 화면 하단 근처에 있는지 확인
if (footerRect.top < windowHeight) {
tabbar.classList.remove('light-mode');
} else {
tabbar.classList.add('light-mode');
}
} else {
tabbar.classList.add('light-mode');
}
}
// 스크롤 시 배경 감지
let floatingTabbarTicking = false;
function updateFloatingTabbarTheme() {
if (!floatingTabbarTicking) {
requestAnimationFrame(() => {
detectFloatingTabbarBrightness();
floatingTabbarTicking = false;
});
floatingTabbarTicking = true;
}
}
// 새로고침 버튼
function floatingTabbarRefresh() {
const refreshBtn = document.querySelector('.floating-tabbar-refresh');
refreshBtn.classList.add('spinning');
setTimeout(() => {
location.reload();
}, 600);
}
// popstate 이벤트 리스너
window.addEventListener('popstate', function(event) {
setTimeout(updateHistoryButtons, 100);
});
// 초기화
document.addEventListener('DOMContentLoaded', function() {
trackPageNavigation();
window.addEventListener('scroll', updateFloatingTabbarTheme);
window.addEventListener('resize', updateFloatingTabbarTheme);
detectFloatingTabbarBrightness();
updateHistoryButtons();
// 터치 피드백
document.querySelectorAll('.floating-tabbar-item, .floating-tabbar-refresh, .floating-tabbar-history-btn').forEach(element => {
element.addEventListener('touchstart', function() {
if (!this.disabled) {
this.style.transform = 'scale(0.95)';
}
});
element.addEventListener('touchend', function() {
setTimeout(() => {
this.style.transform = '';
}, 150);
});
});
});
</script>
기존 플로팅 툴바를 하단에 고정하고 하단에 60px 여백을 줌
- 다음글PWA 팝업 스킨 제작 가이드2025.07.23
댓글목록
등록된 댓글이 없습니다.