float按钮统一

This commit is contained in:
tangly1024
2022-01-11 15:58:57 +08:00
parent 9747097f71
commit 8d1abcdf5b
10 changed files with 199 additions and 232 deletions

View File

@@ -38,8 +38,8 @@ const BLOG = {
showPet: false, // 是否显示宠物挂件
petLink: 'https://cdn.jsdelivr.net/npm/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json', // 挂件模型地址 @see https://github.com/xiazeyu/live2d-widget-models
showToTop: true, // 是否显示回顶
showToBottom: true, // 显示回底
showDarkMode: true, // 显示日间/夜间模式切换
showToBottom: false, // 显示回底
showDarkMode: false, // 显示日间/夜间模式切换
showToc: true, // 移动端显示悬浮目录
showShareBar: false, // 文章分享功能
showRelatePosts: true, // 相关文章推荐

View File

@@ -4,8 +4,6 @@ import Comment from '@/components/Comment'
import RecommendPosts from '@/components/RecommendPosts'
import ShareBar from '@/components/ShareBar'
import TagItem from '@/components/TagItem'
import TocDrawer from '@/components/TocDrawer'
import TocDrawerButton from '@/components/TocDrawerButton'
import formatDate from '@/lib/formatDate'
import { useGlobal } from '@/lib/global'
import { faEye, faFolderOpen } from '@fortawesome/free-solid-svg-icons'
@@ -22,7 +20,6 @@ import 'prismjs/components/prism-typescript'
import { useEffect, useRef } from 'react'
import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
import ArticleCopyright from './ArticleCopyright'
import Live2D from './Live2D'
import WordCount from './WordCount'
/**
@@ -31,8 +28,6 @@ import WordCount from './WordCount'
* @returns
*/
export default function ArticleDetail ({ post, recommendPosts, prev, next }) {
const targetRef = useRef(null)
const drawerRight = useRef(null)
const url = BLOG.link + useRouter().asPath
const { locale } = useGlobal()
const date = formatDate(post?.date?.start_date || post.createdTime, locale.LOCALE)
@@ -55,144 +50,132 @@ export default function ArticleDetail ({ post, recommendPosts, prev, next }) {
}
})
return (<>
<div id="container" ref={targetRef} className="shadow md:hover:shadow-2xl overflow-x-auto flex-grow mx-auto w-screen md:w-full ">
<article itemScope itemType="https://schema.org/Movie"
className="subpixel-antialiased py-10 px-5 lg:pt-24 md:px-24 dark:border-gray-700 bg-white dark:bg-gray-800"
>
return (<div id="container" className="shadow md:hover:shadow-2xl overflow-x-auto flex-grow mx-auto w-screen md:w-full ">
<article itemScope itemType="https://schema.org/Movie"
className="subpixel-antialiased py-10 px-5 lg:pt-24 md:px-24 dark:border-gray-700 bg-white dark:bg-gray-800"
>
<header className='animate__slideInDown animate__animated'>
{post.type && !post.type.includes('Page') && post?.page_cover && (
<div className="w-full relative md:flex-shrink-0 overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img alt={post.title} src={post?.page_cover} className='object-center w-full' />
{/* <div className="w-full h-60 relative lg:h-96 transform duration-200 md:flex-shrink-0 overflow-hidden">
<Image
src={post?.page_cover}
loading="eager"
objectFit="cover"
layout="fill"
alt={post.title}
/>
</div> */}
</div>
)}
{/* 文章Title */}
<div className="font-bold text-3xl text-black dark:text-white font-serif pt-10">
{post.title}
</div>
<section className="flex-wrap flex mt-2 text-gray-400 dark:text-gray-400 font-light leading-8">
<div>
<Link href={`/category/${post.category}`} passHref>
<a className="cursor-pointer text-md mr-2 hover:text-black dark:hover:text-white border-b dark:border-gray-500 border-dashed">
<FontAwesomeIcon icon={faFolderOpen} className="mr-1" />
{post.category}
</a>
</Link>
<span className='mr-2'>|</span>
{post.type[0] !== 'Page' && (<>
<Link
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
passHref
>
<a className="pl-1 mr-2 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 border-b dark:border-gray-500 border-dashed">
{date}
</a>
</Link>
<span className='mr-2'>|</span>
</>)}
<div className="hidden busuanzi_container_page_pv font-light mr-2">
<FontAwesomeIcon icon={faEye} className='mr-1'/>
&nbsp;
<span className="mr-2 busuanzi_value_page_pv"
></span>
<span className='mr-2'>|</span>
</div>
</div>
<div className='flex flex-nowrap whitespace-nowrap items-center font-light text-md'>
<WordCount/>
</div>
</section>
{/* <hr className="mt-2" /> */}
</header>
{/* Notion文章主体 */}
<section id='notion-article' className='px-1'>
{post.blockMap && (
<NotionRenderer
recordMap={post.blockMap}
mapPageUrl={mapPageUrl}
components={{
equation: Equation,
code: Code,
collectionRow: CollectionRow,
collection: Collection
}}
<header className='animate__slideInDown animate__animated'>
{post.type && !post.type.includes('Page') && post?.page_cover && (
<div className="w-full relative md:flex-shrink-0 overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img alt={post.title} src={post?.page_cover} className='object-center w-full' />
{/* <div className="w-full h-60 relative lg:h-96 transform duration-200 md:flex-shrink-0 overflow-hidden">
<Image
src={post?.page_cover}
loading="eager"
objectFit="cover"
layout="fill"
alt={post.title}
/>
)}
</section>
</div> */}
</div>
)}
<section className="px-1 py-2 my-1 text-sm font-light overflow-auto text-gray-600 dark:text-gray-400">
{/* 文章内嵌广告 */}
<ins className="adsbygoogle"
style={{ display: 'block', textAlign: 'center' }}
data-adtest="on"
data-ad-layout="in-article"
data-ad-format="fluid"
data-ad-client="ca-pub-2708419466378217"
data-ad-slot="3806269138"></ins>
</section>
{/* 版权声明 */}
<ArticleCopyright author={BLOG.author} url={url} />
{/* 推荐文章 */}
<RecommendPosts currentPost={post} recommendPosts={recommendPosts} />
{/* 标签列表 */}
<section className="md:flex md:justify-between">
{post.tagItems && (
<div className="flex flex-nowrap leading-8 p-1 py-4 overflow-x-auto">
<div className="hidden md:block dark:text-gray-300 whitespace-nowrap">
{locale.COMMON.TAGS}
</div>
{post.tagItems.map(tag => (
<TagItem key={tag.name} tag={tag} />
))}
</div>
)}
<div>
<ShareBar post={post} />
</div>
</section>
<BlogAround prev={prev} next={next} />
</article>
{/* 评论互动 */}
<div className="lg:px-40 md:hover:shadow-2xl duration-200 shadow w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-gray-800">
<Comment frontMatter={post} />
{/* 文章Title */}
<div className="font-bold text-3xl text-black dark:text-white font-serif pt-10">
{post.title}
</div>
</div>
{/* 悬浮目录按钮 */}
<div className="block lg:hidden">
<TocDrawerButton onClick={() => { drawerRight.current.handleSwitchVisible() }} />
<TocDrawer post={post} cRef={drawerRight} targetRef={targetRef} />
</div>
<section className="flex-wrap flex mt-2 text-gray-400 dark:text-gray-400 font-light leading-8">
<div>
<Link href={`/category/${post.category}`} passHref>
<a className="cursor-pointer text-md mr-2 hover:text-black dark:hover:text-white border-b dark:border-gray-500 border-dashed">
<FontAwesomeIcon icon={faFolderOpen} className="mr-1" />
{post.category}
</a>
</Link>
<span className='mr-2'>|</span>
{/* 宠物 */}
<Live2D/>
{post.type[0] !== 'Page' && (<>
<Link
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
passHref
>
<a className="pl-1 mr-2 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 border-b dark:border-gray-500 border-dashed">
{date}
</a>
</Link>
<span className='mr-2'>|</span>
</>)}
</>)
<div className="hidden busuanzi_container_page_pv font-light mr-2">
<FontAwesomeIcon icon={faEye} className='mr-1'/>
&nbsp;
<span className="mr-2 busuanzi_value_page_pv"
></span>
<span className='mr-2'>|</span>
</div>
</div>
<div className='flex flex-nowrap whitespace-nowrap items-center font-light text-md'>
<WordCount/>
</div>
</section>
{/* <hr className="mt-2" /> */}
</header>
{/* Notion文章主体 */}
<section id='notion-article' className='px-1'>
{post.blockMap && (
<NotionRenderer
recordMap={post.blockMap}
mapPageUrl={mapPageUrl}
components={{
equation: Equation,
code: Code,
collectionRow: CollectionRow,
collection: Collection
}}
/>
)}
</section>
<section className="px-1 py-2 my-1 text-sm font-light overflow-auto text-gray-600 dark:text-gray-400">
{/* 文章内嵌广告 */}
<ins className="adsbygoogle"
style={{ display: 'block', textAlign: 'center' }}
data-adtest="on"
data-ad-layout="in-article"
data-ad-format="fluid"
data-ad-client="ca-pub-2708419466378217"
data-ad-slot="3806269138"></ins>
</section>
{/* 版权声明 */}
<ArticleCopyright author={BLOG.author} url={url} />
{/* 推荐文章 */}
<RecommendPosts currentPost={post} recommendPosts={recommendPosts} />
{/* 标签列表 */}
<section className="md:flex md:justify-between">
{post.tagItems && (
<div className="flex flex-nowrap leading-8 p-1 py-4 overflow-x-auto">
<div className="hidden md:block dark:text-gray-300 whitespace-nowrap">
{locale.COMMON.TAGS}
</div>
{post.tagItems.map(tag => (
<TagItem key={tag.name} tag={tag} />
))}
</div>
)}
<div>
<ShareBar post={post} />
</div>
</section>
<BlogAround prev={prev} next={next} />
</article>
{/* 评论互动 */}
<div className="lg:px-40 md:hover:shadow-2xl duration-200 shadow w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-gray-800">
<Comment frontMatter={post} />
</div>
</div>)
}
const mapPageUrl = id => {

View File

@@ -1,27 +1,13 @@
import { useEffect, useState } from 'react'
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { loadUserThemeFromCookies, saveTheme } from '@/lib/theme'
import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import BLOG from '@/blog.config'
import { loadUserThemeFromCookies, saveTheme } from '@/lib/theme'
export default function FloatDarkModeButton () {
if (!BLOG.widget?.showDarkMode) {
return <></>
}
const [show, switchShow] = useState(false)
const scrollListener = () => {
const scrollY = window.pageYOffset
const shouldShow = scrollY > 100
if (shouldShow !== show) {
switchShow(shouldShow)
}
}
useEffect(() => {
scrollListener()
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
const { changeTheme } = useGlobal()
const userTheme = loadUserThemeFromCookies()
@@ -37,12 +23,8 @@ export default function FloatDarkModeButton () {
return (
<div
id='float-dark-mode-button'
onClick={handleChangeDarkMode}
className={
(show ? '' : ' hidden ') +
' animate__fadeInRight animate__animated animate__faster fixed right-1 bottom-28 z-10 duration-500 text-xs cursor-pointer ' +
' text-black dark:border-gray-500 flex justify-center items-center w-8 h-8 glassmorphism dark:bg-gray-700 dark:text-gray-200'
className={ ' text-black dark:border-gray-500 flex justify-center items-center w-8 h-8 dark:text-gray-200'
}
>
<FontAwesomeIcon

View File

@@ -47,15 +47,12 @@ const JumpToBottomButton = ({ showPercent = false }) => {
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
return (<div id='jump-to-top' className='right-1 fixed flex bottom-36 z-20'>
<div onClick={() => scrollToBottom()}
className={(show ? '' : 'hidden') + ' animate__fadeInRight duration-500 animate__animated animate__faster glassmorphism flex justify-center items-center w-8 h-8 cursor-pointer '}>
return (<div onClick={() => scrollToBottom()}>
<div className='dark:text-gray-200 transform hover:scale-150 text-xs duration-200' title={locale.POST.TOP} >
<FontAwesomeIcon icon={faArrowDown} />
</div>
{showPercent && (<div className='w-10 text-xs dark:text-gray-200'>{percent}</div>)}
</div>
</div>)
</div>)
}
export default JumpToBottomButton

View File

@@ -1,9 +1,8 @@
import React, { useEffect, useState } from 'react'
import { useGlobal } from '@/lib/global'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowUp } from '@fortawesome/free-solid-svg-icons'
import smoothscroll from 'smoothscroll-polyfill'
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { faArrowUp } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React from 'react'
/**
* 跳转到网页顶部
@@ -13,41 +12,16 @@ import BLOG from '@/blog.config'
* @returns {JSX.Element}
* @constructor
*/
const JumpToTopButton = ({ showPercent = false }) => {
const JumpToTopButton = ({ showPercent = true, percent }) => {
if (!BLOG.widget?.showToTop) {
return <></>
}
const { locale } = useGlobal()
const [show, switchShow] = useState(false)
const [percent, changePercent] = useState(0)
const scrollListener = () => {
const targetRef = document.getElementById('wrapper')
const clientHeight = targetRef?.clientHeight
const scrollY = window.pageYOffset
const fullHeight = clientHeight - window.outerHeight
let per = parseFloat(((scrollY / fullHeight * 100)).toFixed(0))
if (per > 100) per = 100
const shouldShow = scrollY > 100 && per > 0
if (shouldShow !== show) {
switchShow(shouldShow)
}
changePercent(per)
}
useEffect(() => {
smoothscroll.polyfill()
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
return (<div id='jump-to-top' className='right-1 fixed flex bottom-44 z-20'>
<div onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
className={(show ? '' : 'hidden') + ' animate__fadeInRight duration-500 animate__animated animate__faster flex justify-center items-center w-8 h-8 glassmorphism cursor-pointer '}>
<div className='dark:text-gray-200 transform hover:scale-150 text-xs duration-200' title={locale.POST.TOP} >
return (<div className='flex space-x-1 transform hover:scale-105 text-xs duration-200' onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} >
<div className='dark:text-gray-200' title={locale.POST.TOP} >
<FontAwesomeIcon icon={faArrowUp} />
</div>
{showPercent && (<div className='w-10 text-xs dark:text-gray-200'>{percent}</div>)}
</div>
{showPercent && (<div className='text-xs dark:text-gray-200'>{percent}%</div>)}
</div>)
}

View File

@@ -6,10 +6,11 @@ import React, { useEffect, useState } from 'react'
* @constructor
*/
const Progress = ({ targetRef, showPercent = true }) => {
const currentRef = targetRef?.current || targetRef
const [percent, changePercent] = useState(0)
const scrollListener = () => {
if (targetRef?.current) {
const clientHeight = targetRef ? (targetRef.current.clientHeight) : 0
if (currentRef) {
const clientHeight = currentRef ? (currentRef.clientHeight) : 0
const scrollY = window.pageYOffset
const fullHeight = clientHeight - window.outerHeight
let per = parseFloat(((scrollY / fullHeight * 100)).toFixed(0))
@@ -24,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
return () => document.removeEventListener('scroll', scrollListener)
}, [percent])
return (<div className='h-4 w-full shadow-2xl bg-gray-400'>
return (<div className='h-4 w-full shadow-2xl bg-gray-400 font-sans'>
<div className='h-4 bg-gray-600 duration-200' style={{ width: `${percent}%` }}>
{showPercent && <div className='text-right text-white text-xs'>{percent}%</div>}
</div>

View File

@@ -56,7 +56,7 @@ const Toc = ({ toc, targetRef }) => {
<div className='w-full'>
<Progress targetRef={targetRef}/>
</div>
<nav className=' overflow-y-auto scroll-hidden'>
<nav className='font-sans overflow-y-auto scroll-hidden'>
{toc.map((tocItem) => {
const id = uuidToId(tocItem.id)
return (

View File

@@ -16,30 +16,11 @@ const TocDrawerButton = (props) => {
return <></>
}
const { locale } = useGlobal()
const [show, switchShow] = useState(false)
const scrollListener = () => {
const scrollY = window.pageYOffset
const shouldShow = scrollY > 100
if (shouldShow !== show) {
switchShow(shouldShow)
}
}
useEffect(() => {
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
})
return (
<div id='toc-drawer-button' className='right-1 fixed bottom-52 z-20'>
<div onClick={props.onClick} className={(show ? 'animate__fadeInRight' : 'hidden') + ' animate__animated animate__faster glassmorphism cursor-pointer' }>
<div className='dark:text-gray-200 text-center transform hover:scale-150 duration-200 text-xs flex justify-center items-center w-8 h-8' title={locale.POST.TOP} >
<FontAwesomeIcon icon={faListOl}/>
</div>
</div>
</div>
)
return (<div onClick={props.onClick} className={'cursor-pointer'}>
<div className='dark:text-gray-200 text-center transform hover:scale-150 duration-200 text-xs flex justify-center items-center' title={locale.POST.TOP} >
<FontAwesomeIcon icon={faListOl}/>
</div>
</div>)
}
export default TocDrawerButton

View File

@@ -10,7 +10,9 @@ import SideAreaRight from '@/components/SideAreaRight'
import TopNav from '@/components/TopNav'
import { useGlobal } from '@/lib/global'
import PropTypes from 'prop-types'
import React, { useRef } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import smoothscroll from 'smoothscroll-polyfill'
/**
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
* @param children
@@ -38,6 +40,7 @@ const BaseLayout = ({
post,
postCount,
sideBarSlot,
floatSlot,
rightAreaSlot,
currentSearch,
currentCategory,
@@ -48,6 +51,28 @@ const BaseLayout = ({
const { onLoading } = useGlobal()
const targetRef = useRef(null)
const [show, switchShow] = useState(false)
const [percent, changePercent] = useState(0) // 页面阅读百分比
const scrollListener = () => {
const targetRef = document.getElementById('wrapper')
const clientHeight = targetRef?.clientHeight
const scrollY = window.pageYOffset
const fullHeight = clientHeight - window.outerHeight
let per = parseFloat(((scrollY / fullHeight * 100)).toFixed(0))
if (per > 100) per = 100
const shouldShow = scrollY > 100 && per > 0
if (shouldShow !== show) {
switchShow(shouldShow)
}
changePercent(per)
}
useEffect(() => {
smoothscroll.polyfill()
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
return (<>
<CommonHead meta={meta} />
@@ -58,9 +83,9 @@ const BaseLayout = ({
<div className='h-0.5 w-full bg-gray-700 dark:bg-gray-600 hidden lg:block'></div>
<main id='wrapper' className='flex justify-center flex-1 mx-auto pb-12'>
<main id='wrapper' className='flex justify-center flex-1 pb-12'>
<SideAreaLeft targetRef={targetRef} post={post} postCount={postCount} tags={tags} currentSearch={currentSearch} currentTag={currentTag} categories={categories} currentCategory={currentCategory}/>
<section id='center' className={`${BLOG.topNavType !== 'normal' ? 'mt-14' : ''} flex-grow md:mt-0 max-w-5xl min-h-screen w-full`} ref={targetRef}>
<section id='center' className={`${BLOG.topNavType !== 'normal' ? 'mt-14' : ''} lg:max-w-3xl xl:max-w-4xl flex-grow md:mt-0 min-h-screen w-full`} ref={targetRef}>
{onLoading
? <LoadingCover/>
: <>
@@ -71,10 +96,16 @@ const BaseLayout = ({
<SideAreaRight targetRef={targetRef} post={post} slot={rightAreaSlot} postCount={postCount} tags={tags} currentSearch={currentSearch} currentTag={currentTag} categories={categories} currentCategory={currentCategory}/>
</main>
<div className='right-4 bottom-2 fixed justify-end z-20 glassmorphism rounded font-sans'>
<div className={(show ? 'animate__animated ' : 'hidden') + ' m-3 animate__fadeInUp justify-center duration-500 animate__faster flex space-x-2 items-center cursor-pointer '}>
<JumpToTopButton percent={percent}/>
<JumpToBottomButton />
<FloatDarkModeButton/>
{floatSlot}
</div>
</div>
<Footer title={meta.title}/>
<JumpToTopButton showPercent={false} />
<JumpToBottomButton showPercent={false}/>
<FloatDarkModeButton/>
</>
)
}

View File

@@ -1,10 +1,14 @@
import BLOG from '@/blog.config'
import ArticleDetail from '@/components/ArticleDetail'
import Live2D from '@/components/Live2D'
import TocDrawer from '@/components/TocDrawer'
import TocDrawerButton from '@/components/TocDrawerButton'
import BaseLayout from '@/layouts/BaseLayout'
import { getAllPosts, getPostBlocks } from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import Custom404 from '@/pages/404'
import { getPageTableOfContents } from 'notion-utils'
import { useRef } from 'react'
/**
* 根据notion的slug访问页面
@@ -31,6 +35,10 @@ const Slug = ({
tags: post.tags
}
const drawerRight = useRef(null)
const targetRef = typeof window !== 'undefined' ? document.getElementById('container') : null
const floatSlot = <div className="block lg:hidden"><TocDrawerButton onClick={() => { drawerRight?.current?.handleSwitchVisible() }} /></div>
return (
<BaseLayout
meta={meta}
@@ -39,6 +47,7 @@ const Slug = ({
postCount={postCount}
latestPosts={latestPosts}
categories={categories}
floatSlot={floatSlot}
>
<ArticleDetail
post={post}
@@ -46,6 +55,15 @@ const Slug = ({
prev={prev}
next={next}
/>
{/* 悬浮目录按钮 */}
<div className="block lg:hidden">
<TocDrawer post={post} cRef={drawerRight} targetRef={targetRef} />
</div>
{/* 宠物 */}
<Live2D/>
</BaseLayout>
)
}