WordPress Single Post 文章页美化:响应式表格、图片优化与自动悬浮 Table of Contents(CSS + JS)

Css

css 复制代码
/* =========================================================
   UNIVERSAL SINGLE POST ENHANCEMENT
   Article typography + responsive images + responsive tables
   + floating left TOC (desktop) + mobile TOC drawer
   Version: 1.0
========================================================= */

body.ws-single-post-enhanced {
    --ws-primary: #1C6BAE;
    --ws-primary-dark: #155487;
    --ws-primary-soft: #eef6fb;

    --ws-heading: #17354c;
    --ws-text: #344757;
    --ws-muted: #687986;

    --ws-border: #dce5eb;
    --ws-border-light: #e9eff3;

    --ws-bg: #ffffff;
    --ws-bg-soft: #f7fafc;

    --ws-radius: 8px;

    color: var(--ws-text);
}


/* =========================================================
   1. ARTICLE TYPOGRAPHY
========================================================= */

body.ws-single-post-enhanced .ws-article-content {
    color: var(--ws-text);
}

body.ws-single-post-enhanced .ws-article-content p {
    margin: 0 0 20px !important;

    color: var(--ws-text) !important;

    font-size: 16px !important;
    font-weight: 400 !important;

    line-height: 1.95 !important;
    letter-spacing: .015em !important;
}

body.ws-single-post-enhanced .ws-article-content p strong {
    color: #173b59;
    font-weight: 650;
}


/* =========================================================
   2. H2
========================================================= */

body.ws-single-post-enhanced .ws-article-content h2 {
    display: block !important;

    margin: 48px 0 23px !important;
    padding: 13px 17px 13px 18px !important;

    color: var(--ws-heading) !important;

    font-size: 24px !important;
    font-weight: 700 !important;
    line-height: 1.55 !important;
    letter-spacing: .01em !important;

    background:
        linear-gradient(
            90deg,
            rgba(28, 107, 174, .085),
            rgba(28, 107, 174, .018) 75%,
            transparent
        ) !important;

    border-top: 0 !important;
    border-right: 0 !important;
    border-bottom: 1px solid #e7eef3 !important;
    border-left: 4px solid var(--ws-primary) !important;

    border-radius: 0 6px 6px 0 !important;

    scroll-margin-top: 100px;
}

body.ws-single-post-enhanced .ws-article-content h2:first-of-type {
    margin-top: 38px !important;
}


/* =========================================================
   3. H3
========================================================= */

body.ws-single-post-enhanced .ws-article-content h3 {
    position: relative !important;

    display: block !important;

    margin: 33px 0 16px !important;
    padding: 0 0 10px 16px !important;

    color: var(--ws-heading) !important;

    font-size: 19px !important;
    font-weight: 700 !important;
    line-height: 1.6 !important;

    border: 0 !important;
    border-bottom: 1px solid var(--ws-border) !important;

    scroll-margin-top: 100px;
}

body.ws-single-post-enhanced .ws-article-content h3::before {
    content: "" !important;

    position: absolute !important;

    left: 0 !important;
    top: .7em !important;

    width: 6px !important;
    height: 6px !important;

    border-radius: 50% !important;

    background: var(--ws-primary) !important;
}


/* =========================================================
   4. LINKS
========================================================= */

body.ws-single-post-enhanced .ws-article-content a {
    color: var(--ws-primary);
    font-weight: 500;

    text-decoration: underline;
    text-decoration-color: rgba(28, 107, 174, .28);
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;

    transition:
        color .18s ease,
        text-decoration-color .18s ease;
}

body.ws-single-post-enhanced .ws-article-content a:hover {
    color: var(--ws-primary-dark);
    text-decoration-color: var(--ws-primary-dark);
}


/* =========================================================
   5. LISTS
========================================================= */

body.ws-single-post-enhanced .ws-article-content ul,
body.ws-single-post-enhanced .ws-article-content ol {
    margin: 18px 0 27px !important;

    padding:
        18px
        22px
        18px
        43px !important;

    background: #f8fafc !important;

    border: 1px solid #e7edf1 !important;
    border-radius: 7px !important;
}

body.ws-single-post-enhanced .ws-article-content ul li,
body.ws-single-post-enhanced .ws-article-content ol li {
    margin: 7px 0 !important;

    color: #3c4f5f !important;

    font-size: 15px !important;
    line-height: 1.8 !important;
}

body.ws-single-post-enhanced .ws-article-content ul li::marker,
body.ws-single-post-enhanced .ws-article-content ol li::marker {
    color: var(--ws-primary) !important;
    font-weight: 700 !important;
}


/* =========================================================
   6. ARTICLE IMAGES
========================================================= */

body.ws-single-post-enhanced .ws-article-content .ws-article-image {
    display: block !important;

    width: min(100%, 680px) !important;
    max-width: 680px !important;

    margin: 30px auto 35px !important;
    padding: 0 !important;

    text-align: center !important;

    float: none !important;
}

body.ws-single-post-enhanced .ws-article-content .ws-article-image img {
    display: block !important;

    width: 100% !important;
    max-width: 100% !important;
    height: auto !important;

    margin: 0 auto !important;

    border: 0 !important;
    border-radius: 8px !important;

    object-fit: contain !important;

    box-shadow:
        0 3px 18px rgba(24, 53, 74, .075) !important;
}

body.ws-single-post-enhanced .ws-article-content img.ws-article-inline-image {
    display: block !important;

    width: min(100%, 680px) !important;
    max-width: 680px !important;
    height: auto !important;

    margin: 30px auto 35px !important;

    border-radius: 8px !important;

    object-fit: contain !important;

    box-shadow:
        0 3px 18px rgba(24, 53, 74, .075) !important;
}


/* =========================================================
   7. HERO / FEATURED IMAGE
========================================================= */

body.ws-single-post-enhanced .ws-article-hero {
    width: min(100%, 760px) !important;
    max-width: 760px !important;

    margin-left: auto !important;
    margin-right: auto !important;
    margin-bottom: 30px !important;
}

body.ws-single-post-enhanced .ws-article-hero img,
body.ws-single-post-enhanced img.ws-article-hero {
    display: block !important;

    width: 100% !important;
    max-width: 100% !important;
    height: auto !important;

    margin-left: auto !important;
    margin-right: auto !important;

    border-radius: 9px !important;

    box-shadow:
        0 4px 20px rgba(24, 53, 74, .07) !important;
}


/* =========================================================
   8. BLOCKQUOTE
========================================================= */

body.ws-single-post-enhanced .ws-article-content blockquote {
    position: relative !important;

    margin: 28px 0 !important;

    padding:
        21px
        24px
        21px
        28px !important;

    background: #f1f7fb !important;

    border: 0 !important;
    border-left: 4px solid var(--ws-primary) !important;

    border-radius: 0 7px 7px 0 !important;
}

body.ws-single-post-enhanced .ws-article-content blockquote p {
    margin: 0 !important;

    color: #24455d !important;

    font-size: 15.5px !important;
    line-height: 1.85 !important;
}


/* =========================================================
   9. RESPONSIVE TABLE WRAPPER
========================================================= */

body.ws-single-post-enhanced .ws-article-content .ws-table-scroll {
    display: block !important;

    position: relative !important;

    width: 100% !important;
    max-width: 100% !important;

    margin: 27px 0 38px !important;
    padding: 0 !important;

    overflow-x: auto !important;
    overflow-y: hidden !important;

    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;

    border: 1px solid var(--ws-border) !important;
    border-radius: 8px !important;

    background: #fff !important;

    box-shadow:
        0 3px 16px rgba(24, 53, 74, .055) !important;
}

body.ws-single-post-enhanced .ws-table-scroll::-webkit-scrollbar {
    height: 7px !important;
}

body.ws-single-post-enhanced .ws-table-scroll::-webkit-scrollbar-track {
    background: #eef3f6 !important;
}

body.ws-single-post-enhanced .ws-table-scroll::-webkit-scrollbar-thumb {
    background: #a8c3d8 !important;
    border-radius: 10px !important;
}

body.ws-single-post-enhanced .ws-table-scroll::-webkit-scrollbar-thumb:hover {
    background: var(--ws-primary) !important;
}


/* =========================================================
   10. TABLE
========================================================= */

body.ws-single-post-enhanced .ws-article-content .ws-article-table {
    width: 100% !important;
    max-width: none !important;

    margin: 0 !important;
    padding: 0 !important;

    border: 0 !important;

    border-collapse: separate !important;
    border-spacing: 0 !important;

    table-layout: auto !important;

    background: #fff !important;

    color: var(--ws-text) !important;

    font-size: 14px !important;
    line-height: 1.65 !important;
}


/* =========================================================
   11. TABLE HEADER
========================================================= */

body.ws-single-post-enhanced .ws-article-table thead,
body.ws-single-post-enhanced .ws-article-table thead tr {
    background: var(--ws-primary) !important;
}

body.ws-single-post-enhanced .ws-article-table thead th {
    padding: 13px 15px !important;

    color: #fff !important;
    background: var(--ws-primary) !important;

    border: 0 !important;
    border-right:
        1px solid rgba(255,255,255,.18) !important;
    border-bottom: 0 !important;

    font-size: 13.5px !important;
    font-weight: 650 !important;
    line-height: 1.55 !important;

    text-align: left !important;
    vertical-align: middle !important;

    white-space: nowrap !important;
}

body.ws-single-post-enhanced .ws-article-table thead th:last-child {
    border-right: 0 !important;
}


/* =========================================================
   12. TABLE CELLS
========================================================= */

body.ws-single-post-enhanced .ws-article-table tbody td {
    padding: 13px 15px !important;

    color: #3c4d5b !important;
    background: #fff !important;

    border: 0 !important;
    border-right:
        1px solid var(--ws-border-light) !important;
    border-bottom:
        1px solid var(--ws-border-light) !important;

    text-align: left !important;
    vertical-align: top !important;

    word-break: normal !important;
}

body.ws-single-post-enhanced .ws-article-table tbody td:last-child {
    border-right: 0 !important;
}

body.ws-single-post-enhanced .ws-article-table tbody tr:last-child td {
    border-bottom: 0 !important;
}

body.ws-single-post-enhanced
.ws-article-table
tbody
tr:nth-child(even)
td {
    background: #f7fafc !important;
}

body.ws-single-post-enhanced
.ws-article-table
tbody
td:first-child {
    color: #24455e !important;
    font-weight: 600 !important;
    background: #f4f8fb !important;
}

body.ws-single-post-enhanced
.ws-article-table
tbody
tr:nth-child(even)
td:first-child {
    background: #eef5f9 !important;
}

@media (hover:hover) {
    body.ws-single-post-enhanced
    .ws-article-table
    tbody
    tr:hover
    td {
        background: #edf6fc !important;
    }
}


/* =========================================================
   13. META / AUTHOR (OPTIONAL COMMON WORDPRESS STYLES)
========================================================= */

body.ws-single-post-enhanced .post-meta {
    margin-bottom: 18px;

    color: #788796;

    font-size: 12.5px;
}

body.ws-single-post-enhanced .post-meta a {
    color: #637889;
}

body.ws-single-post-enhanced .post-author {
    margin-top: 40px;
    padding: 24px;

    border: 1px solid var(--ws-border);
    border-radius: 10px;

    background: #fafcfd;
}


/* =========================================================
   14. DESKTOP FLOATING LEFT TOC
========================================================= */

#ws-floating-toc {
    position: fixed;

    left: 16px;
    top: 50%;

    z-index: 90;

    width: 250px;
    max-height: 64vh;

    transform: translateY(-50%);

    background: #fff;

    border: 1px solid var(--ws-border);
    border-radius: 10px;

    box-shadow:
        0 8px 28px rgba(23, 54, 77, .09);

    overflow: hidden;

    transition:
        width .25s ease,
        box-shadow .25s ease;
}


/* -------------------------
   TOC Header
------------------------- */

.ws-floating-toc-head {
    display: flex;
    align-items: center;
    justify-content: space-between;

    min-height: 50px;

    padding: 0 11px 0 16px;

    background: #f8fbfd;

    border-bottom: 1px solid #e7eef3;
}

.ws-floating-toc-title {
    display: flex;
    align-items: center;
    gap: 9px;

    min-width: 0;

    color: #18374f;

    font-size: 14px;
    font-weight: 700;

    white-space: nowrap;

    cursor: default;
}

.ws-floating-toc-title::before {
    content: "";

    width: 4px;
    height: 17px;

    flex: 0 0 4px;

    border-radius: 3px;

    background: var(--ws-primary);
}

.ws-floating-toc-toggle {
    display: flex;
    align-items: center;
    justify-content: center;

    flex: 0 0 30px;

    width: 30px;
    height: 30px;

    padding: 0;

    border: 0;
    border-radius: 5px;

    background: transparent;
    color: #617789;

    cursor: pointer;

    transition:
        color .18s ease,
        background .18s ease;
}

.ws-floating-toc-toggle:hover {
    color: var(--ws-primary);
    background: #eaf3f9;
}

.ws-floating-toc-toggle::before {
    content: "";

    width: 7px;
    height: 7px;

    border-left: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;

    transform: rotate(45deg);

    transition: transform .2s ease;
}


/* -------------------------
   Progress
------------------------- */

.ws-floating-toc-progress {
    height: 2px;
    background: #edf2f5;
}

.ws-floating-toc-progress-bar {
    width: 0;
    height: 100%;

    background: var(--ws-primary);

    transition: width .08s linear;
}


/* -------------------------
   TOC Body
------------------------- */

.ws-floating-toc-body {
    max-height: calc(64vh - 54px);

    padding: 9px 8px 12px;

    overflow-y: auto;
    overflow-x: hidden;

    overscroll-behavior: contain;
}

.ws-floating-toc-body::-webkit-scrollbar {
    width: 4px;
}

.ws-floating-toc-body::-webkit-scrollbar-track {
    background: transparent;
}

.ws-floating-toc-body::-webkit-scrollbar-thumb {
    background: #c7d5df;
    border-radius: 5px;
}


/* -------------------------
   TOC List
------------------------- */

.ws-floating-toc-list {
    list-style: none !important;

    margin: 0 !important;
    padding: 0 !important;

    background: transparent !important;
    border: 0 !important;
}

.ws-floating-toc-item {
    margin: 1px 0 !important;
    padding: 0 !important;
}

.ws-floating-toc-link {
    position: relative;

    display: block;

    padding: 7px 9px 7px 12px;

    color: #42576a !important;

    border-radius: 5px;

    font-size: 12.5px;
    line-height: 1.55;

    text-decoration: none !important;

    word-break: break-word;

    transition:
        background .15s ease,
        color .15s ease;
}

.ws-floating-toc-link:hover {
    color: var(--ws-primary) !important;
    background: #f0f7fb;
}

.ws-floating-toc-item.level-3 .ws-floating-toc-link {
    padding-left: 27px;

    color: #718191 !important;

    font-size: 11.8px;
}

.ws-floating-toc-item.level-3 .ws-floating-toc-link::before {
    content: "";

    position: absolute;

    left: 15px;
    top: 15px;

    width: 4px;
    height: 4px;

    border-radius: 50%;

    background: #b8c7d2;
}

.ws-floating-toc-link.is-active {
    color: var(--ws-primary) !important;

    background: #edf6fc;

    font-weight: 600;
}

.ws-floating-toc-link.is-active::after {
    content: "";

    position: absolute;

    top: 7px;
    bottom: 7px;
    left: 0;

    width: 3px;

    background: var(--ws-primary);

    border-radius: 3px;
}


/* =========================================================
   15. COLLAPSED DESKTOP TOC
========================================================= */

#ws-floating-toc.is-collapsed {
    width: 46px;
    max-height: none;

    border-radius: 0 8px 8px 0;
}

#ws-floating-toc.is-collapsed .ws-floating-toc-head {
    height: 118px;
    min-height: 118px;

    padding: 9px 0;

    flex-direction: column;
    justify-content: space-between;

    border-bottom: 0;
}

#ws-floating-toc.is-collapsed .ws-floating-toc-title {
    writing-mode: vertical-rl;

    transform: rotate(180deg);

    font-size: 12px;

    letter-spacing: .12em;

    cursor: pointer;
}

#ws-floating-toc.is-collapsed .ws-floating-toc-title::before {
    display: none;
}

#ws-floating-toc.is-collapsed .ws-floating-toc-progress,
#ws-floating-toc.is-collapsed .ws-floating-toc-body {
    display: none;
}

#ws-floating-toc.is-collapsed .ws-floating-toc-toggle::before {
    transform: rotate(225deg);
}


/* =========================================================
   16. LAPTOP / LARGE DESKTOP
========================================================= */

@media (min-width: 992px) and (max-width: 1439px) {
    #ws-floating-toc {
        left: 8px;

        width: 220px;
        max-height: 60vh;
    }

    .ws-floating-toc-body {
        max-height: calc(60vh - 54px);
    }

    .ws-floating-toc-link {
        font-size: 11.8px;
    }

    .ws-floating-toc-item.level-3 .ws-floating-toc-link {
        font-size: 11.2px;
    }

    #ws-floating-toc.is-collapsed {
        width: 46px;
    }
}

@media (min-width: 1800px) {
    #ws-floating-toc {
        left: 28px;

        width: 270px;
        max-height: 62vh;
    }

    .ws-floating-toc-body {
        max-height: calc(62vh - 54px);
    }
}

@media (max-height: 700px) and (min-width: 992px) {
    #ws-floating-toc {
        max-height: 72vh;
    }

    .ws-floating-toc-body {
        max-height: calc(72vh - 54px);
    }
}


/* =========================================================
   17. MOBILE TOC
========================================================= */

#ws-mobile-toc-trigger,
#ws-mobile-toc-drawer,
#ws-mobile-toc-mask {
    display: none;
}

@media (max-width: 991px) {

    #ws-floating-toc {
        display: none !important;
    }

    #ws-mobile-toc-trigger {
        display: flex;

        position: fixed;

        left: 14px;
        bottom: 68px;

        z-index: 9990;

        align-items: center;
        justify-content: center;

        gap: 7px;

        min-width: 68px;
        height: 42px;

        padding: 0 15px;

        border: 0;
        border-radius: 22px;

        background: var(--ws-primary);
        color: #fff;

        font-size: 12px;
        font-weight: 650;

        cursor: pointer;

        box-shadow:
            0 6px 22px rgba(14, 63, 101, .25);

        transition:
            transform .18s ease,
            background .18s ease;
    }

    #ws-mobile-toc-trigger::before {
        content: "☰";
        font-size: 14px;
    }

    #ws-mobile-toc-trigger:hover {
        background: var(--ws-primary-dark);
    }

    #ws-mobile-toc-trigger:active {
        transform: scale(.96);
    }


    /* overlay */

    #ws-mobile-toc-mask {
        display: block;

        position: fixed;

        inset: 0;

        z-index: 9991;

        background: rgba(13, 30, 43, .38);

        opacity: 0;

        pointer-events: none;

        transition: opacity .25s ease;
    }


    /* centered mobile drawer */

    #ws-mobile-toc-drawer {
        display: flex;

        position: fixed;

        top: 50%;
        left: 0;
        bottom: auto;

        z-index: 9992;

        width: min(86vw, 340px);
        max-height: 68vh;

        flex-direction: column;

        background: #fff;

        border-radius: 0 12px 12px 0;

        box-shadow:
            10px 0 32px rgba(8, 32, 49, .19);

        overflow: hidden;

        transform: translate(-105%, -50%);

        transition:
            transform .28s cubic-bezier(.22, .7, .22, 1);
    }

    .ws-mobile-toc-drawer-head {
        display: flex;

        flex: 0 0 54px;

        height: 54px;
        min-height: 54px;

        align-items: center;
        justify-content: space-between;

        padding: 0 15px 0 18px;

        border-bottom: 1px solid var(--ws-border);

        background: #f8fbfd;
    }

    .ws-mobile-toc-drawer-head strong {
        display: flex;
        align-items: center;
        gap: 9px;

        color: #18374f;

        font-size: 15px;
        font-weight: 700;
    }

    .ws-mobile-toc-drawer-head strong::before {
        content: "";

        width: 4px;
        height: 18px;

        border-radius: 3px;

        background: var(--ws-primary);
    }

    .ws-mobile-toc-close {
        display: flex;

        align-items: center;
        justify-content: center;

        width: 34px;
        height: 34px;

        padding: 0;

        border: 0;
        border-radius: 50%;

        background: #eaf1f5;

        color: #536878;

        font-size: 20px;

        cursor: pointer;
    }

    .ws-mobile-toc-drawer-body {
        flex: 1;

        max-height: calc(68vh - 54px);

        padding: 9px 10px 12px;

        overflow-y: auto;
        overflow-x: hidden;

        -webkit-overflow-scrolling: touch;
        overscroll-behavior: contain;
    }

    .ws-mobile-toc-drawer-body .ws-floating-toc-link {
        padding-top: 10px;
        padding-bottom: 10px;

        font-size: 13.5px;
    }

    .ws-mobile-toc-drawer-body
    .ws-floating-toc-item.level-3
    .ws-floating-toc-link {
        font-size: 12.5px;
    }

    body.ws-toc-mobile-open {
        overflow: hidden;
    }

    body.ws-toc-mobile-open #ws-mobile-toc-mask {
        opacity: 1;
        pointer-events: auto;
    }

    body.ws-toc-mobile-open #ws-mobile-toc-drawer {
        transform: translate(0, -50%);
    }
}


/* =========================================================
   18. MOBILE ARTICLE
========================================================= */

@media (max-width: 767px) {

    body.ws-single-post-enhanced .ws-article-content p {
        margin-bottom: 17px !important;

        font-size: 14.5px !important;
        line-height: 1.9 !important;
    }

    body.ws-single-post-enhanced .ws-article-content h2 {
        margin: 36px 0 19px !important;
        padding: 10px 11px 10px 13px !important;

        font-size: 19px !important;

        border-left-width: 3px !important;
    }

    body.ws-single-post-enhanced .ws-article-content h3 {
        margin: 27px 0 14px !important;

        font-size: 16.5px !important;
    }

    body.ws-single-post-enhanced
    .ws-article-content
    .ws-article-image,
    body.ws-single-post-enhanced
    .ws-article-content
    img.ws-article-inline-image {
        width: 100% !important;
        max-width: 100% !important;

        margin: 23px auto 28px !important;
    }

    body.ws-single-post-enhanced
    .ws-article-content
    .ws-article-image
    img {
        width: 100% !important;
        max-width: 100% !important;

        border-radius: 6px !important;
    }

    body.ws-single-post-enhanced .ws-article-hero {
        width: 100% !important;
        max-width: 100% !important;

        margin-bottom: 24px !important;
    }

    body.ws-single-post-enhanced .ws-article-content ul,
    body.ws-single-post-enhanced .ws-article-content ol {
        padding:
            16px
            15px
            16px
            38px !important;

        margin-bottom: 23px !important;
    }

    body.ws-single-post-enhanced .ws-article-content ul li,
    body.ws-single-post-enhanced .ws-article-content ol li {
        font-size: 14.5px !important;
    }

    body.ws-single-post-enhanced .ws-article-content blockquote {
        padding:
            18px
            17px
            18px
            22px !important;

        margin: 25px 0 !important;
    }


    /* Mobile tables */

    body.ws-single-post-enhanced
    .ws-article-content
    .ws-table-scroll {
        width: calc(100% + 12px) !important;
        max-width: calc(100% + 12px) !important;

        margin-left: -6px !important;
        margin-right: -6px !important;

        border-radius: 6px !important;
    }

    body.ws-single-post-enhanced
    .ws-article-content
    .ws-article-table {
        width: max-content !important;

        min-width:
            var(--ws-table-min-width, 680px) !important;

        table-layout: auto !important;

        font-size: 13px !important;
    }

    body.ws-single-post-enhanced
    .ws-article-table
    th,
    body.ws-single-post-enhanced
    .ws-article-table
    td {
        padding: 11px 12px !important;

        min-width: 130px !important;
    }

    body.ws-single-post-enhanced
    .ws-article-table
    th:first-child,
    body.ws-single-post-enhanced
    .ws-article-table
    td:first-child {
        position: sticky !important;

        left: 0 !important;

        z-index: 3 !important;

        min-width: 125px !important;

        box-shadow:
            5px 0 9px rgba(24,53,74,.06) !important;
    }

    body.ws-single-post-enhanced
    .ws-article-table
    th:first-child {
        z-index: 4 !important;

        color: #fff !important;
        background: var(--ws-primary) !important;
    }

    body.ws-single-post-enhanced
    .ws-article-table
    tbody
    tr:nth-child(odd)
    td:first-child {
        background: #f4f8fb !important;
    }

    body.ws-single-post-enhanced
    .ws-article-table
    tbody
    tr:nth-child(even)
    td:first-child {
        background: #eef5f9 !important;
    }
}


/* =========================================================
   19. SMALL PHONE
========================================================= */

@media (max-width: 480px) {

    #ws-mobile-toc-drawer {
        width: 88vw;
        max-height: 68vh;
    }

    .ws-mobile-toc-drawer-body {
        max-height: calc(68vh - 54px);
    }
}


/* =========================================================
   20. SHORT MOBILE SCREEN
========================================================= */

@media (max-height: 650px) and (max-width: 991px) {

    #ws-mobile-toc-drawer {
        max-height: 76vh;
    }

    .ws-mobile-toc-drawer-body {
        max-height: calc(76vh - 54px);
    }
}


/* =========================================================
   21. ACCESSIBILITY
========================================================= */

body.ws-single-post-enhanced a:focus-visible,
body.ws-single-post-enhanced button:focus-visible {
    outline: 2px solid var(--ws-primary);
    outline-offset: 3px;
}


/* =========================================================
   OPTIONAL
   Uncomment if you want to hide WordPress comments.
========================================================= */

/*
body.single-post .comments-area,
body.single-post .comment-respond,
body.single-post .meta-comments,
body.single-post .widget_recent_comments {
    display: none !important;
}
*/

Js

javascript 复制代码
/**
 * Universal Single Post Enhancer
 * ---------------------------------------------------------
 * Features:
 * 1. Auto-detect article content
 * 2. Add professional article styles through CSS classes
 * 3. Responsive images
 * 4. Responsive tables with horizontal scrolling
 * 5. Desktop floating left TOC, open by default
 * 6. Desktop TOC can collapse
 * 7. Mobile TOC closed by default
 * 8. Mobile TOC opens as a centered left drawer
 * 9. Active section highlight
 * 10. Reading progress
 * 11. Smooth scrolling
 *
 * Designed for WordPress / Porto / Elementor / Gutenberg,
 * but can also be adapted to other CMS templates.
 */

(function () {
    'use strict';

    document.addEventListener('DOMContentLoaded', function () {

        /* =====================================================
           CONFIG
        ===================================================== */

        var CONFIG = {

            /*
             * true:
             * only run on WordPress single post pages.
             *
             * false:
             * can also run on other CMS/article pages.
             */
            onlySinglePost: true,

            minHeadingCount: 2,

            headingSelector: 'h2, h3',

            headerOffsetDesktop: 95,
            headerOffsetMobile: 75,

            desktopBreakpoint: 992,

            /*
             * Desktop TOC default:
             * false = expanded
             * true  = collapsed
             */
            desktopCollapsedByDefault: false,

            articleCandidates: [
                '.entry-content',
                '.post-content',
                '.single-post-content',
                '.single-content',
                '.article-content',
                '.elementor-widget-theme-post-content .elementor-widget-container',
                '.main-content .elementor-heading-title',
                '.main-content article',
                'article .entry-content',
                'article'
            ],

            heroCandidates: [
                '.post-image.single',
                '.post-thumbnail',
                '.entry-thumbnail',
                '.featured-image',
                '.single-post-thumbnail'
            ]
        };


        /* =====================================================
           1. PAGE CHECK
        ===================================================== */

        if (
            CONFIG.onlySinglePost &&
            !document.body.classList.contains('single-post')
        ) {
            return;
        }


        /* =====================================================
           2. FIND BEST ARTICLE CONTAINER
        ===================================================== */

        function uniqueElements(elements) {

            var result = [];

            elements.forEach(function (element) {

                if (
                    element &&
                    result.indexOf(element) === -1
                ) {
                    result.push(element);
                }

            });

            return result;
        }


        var candidates = [];

        CONFIG.articleCandidates.forEach(function (selector) {

            document
                .querySelectorAll(selector)
                .forEach(function (element) {
                    candidates.push(element);
                });

        });

        candidates = uniqueElements(candidates);


        var article = null;
        var bestScore = -1;


        candidates.forEach(function (candidate) {

            var headings =
                candidate.querySelectorAll(
                    CONFIG.headingSelector
                ).length;

            var paragraphs =
                candidate.querySelectorAll('p').length;

            var textLength =
                (candidate.textContent || '').trim().length;

            /*
             * Headings are weighted heavily,
             * so sidebars / footers are unlikely to win.
             */
            var score =
                headings * 100 +
                paragraphs * 4 +
                Math.min(textLength / 500, 40);


            if (score > bestScore) {

                bestScore = score;
                article = candidate;

            }

        });


        if (!article) {
            return;
        }


        var headings = Array.prototype.slice.call(
            article.querySelectorAll(
                CONFIG.headingSelector
            )
        );


        if (headings.length < CONFIG.minHeadingCount) {
            return;
        }


        document.body.classList.add(
            'ws-single-post-enhanced'
        );

        article.classList.add(
            'ws-article-content'
        );


        /* =====================================================
           3. HERO IMAGE
        ===================================================== */

        CONFIG.heroCandidates.some(function (selector) {

            var hero =
                document.querySelector(selector);

            if (
                hero &&
                !article.contains(hero)
            ) {

                hero.classList.add(
                    'ws-article-hero'
                );

                return true;
            }

            return false;

        });


        /* =====================================================
           4. ARTICLE IMAGES
        ===================================================== */

        article
            .querySelectorAll('figure')
            .forEach(function (figure) {

                var hasImage =
                    figure.querySelector('img');

                var hasTable =
                    figure.querySelector('table');

                var isGallery =
                    figure.classList.contains('wp-block-gallery') ||
                    figure.closest('.wp-block-gallery') ||
                    figure.classList.contains('gallery');

                if (
                    hasImage &&
                    !hasTable &&
                    !isGallery
                ) {
                    figure.classList.add(
                        'ws-article-image'
                    );
                }

            });


        article
            .querySelectorAll('img')
            .forEach(function (img) {

                if (
                    !img.closest('figure') &&
                    !img.closest('.gallery') &&
                    !img.closest('.wp-block-gallery')
                ) {
                    img.classList.add(
                        'ws-article-inline-image'
                    );
                }

            });


        /* =====================================================
           5. RESPONSIVE TABLES
        ===================================================== */

        article
            .querySelectorAll('table')
            .forEach(function (table) {

                table.classList.add(
                    'ws-article-table'
                );


                var wrapper =
                    table.closest(
                        '.wp-block-table, .ws-table-scroll'
                    );


                if (!wrapper) {

                    wrapper =
                        document.createElement('div');

                    wrapper.className =
                        'ws-table-scroll';


                    table.parentNode.insertBefore(
                        wrapper,
                        table
                    );


                    wrapper.appendChild(table);

                } else {

                    wrapper.classList.add(
                        'ws-table-scroll'
                    );

                }


                var columnCount = 0;

                Array.prototype.slice
                    .call(table.rows)
                    .forEach(function (row) {

                        columnCount =
                            Math.max(
                                columnCount,
                                row.cells.length
                            );

                    });


                var minWidth = 560;

                if (columnCount === 3) {
                    minWidth = 660;
                }

                if (columnCount === 4) {
                    minWidth = 760;
                }

                if (columnCount >= 5) {
                    minWidth = 880;
                }


                table.style.setProperty(
                    '--ws-table-min-width',
                    minWidth + 'px'
                );


                wrapper.setAttribute(
                    'tabindex',
                    '0'
                );

                wrapper.setAttribute(
                    'role',
                    'region'
                );

                wrapper.setAttribute(
                    'aria-label',
                    'Horizontally scrollable table'
                );

            });


        /* =====================================================
           6. HEADING IDS
        ===================================================== */

        headings.forEach(function (heading, index) {

            if (!heading.id) {

                heading.id =
                    'ws-article-section-' +
                    (index + 1);

            }

        });


        /* =====================================================
           7. BUILD TOC LIST
        ===================================================== */

        function createTocList() {

            var list =
                document.createElement('ul');

            list.className =
                'ws-floating-toc-list';


            headings.forEach(function (heading) {

                var level =
                    heading.tagName === 'H3'
                        ? '3'
                        : '2';


                var li =
                    document.createElement('li');

                li.className =
                    'ws-floating-toc-item level-' +
                    level;


                var link =
                    document.createElement('a');

                link.className =
                    'ws-floating-toc-link';

                link.href =
                    '#' + heading.id;

                link.dataset.target =
                    heading.id;

                link.textContent =
                    heading.textContent.trim();


                li.appendChild(link);

                list.appendChild(li);

            });


            return list;

        }


        /* =====================================================
           8. DESKTOP FLOATING TOC
        ===================================================== */

        var toc =
            document.createElement('nav');


        toc.id =
            'ws-floating-toc';


        toc.setAttribute(
            'aria-label',
            '記事のContents'
        );


        if (CONFIG.desktopCollapsedByDefault) {

            toc.classList.add(
                'is-collapsed'
            );

        }


        var tocHead =
            document.createElement('div');

        tocHead.className =
            'ws-floating-toc-head';


        var tocTitle =
            document.createElement('div');

        tocTitle.className =
            'ws-floating-toc-title';

        tocTitle.textContent =
            'Contents';


        var tocToggle =
            document.createElement('button');

        tocToggle.type =
            'button';

        tocToggle.className =
            'ws-floating-toc-toggle';


        tocToggle.setAttribute(
            'aria-expanded',
            CONFIG.desktopCollapsedByDefault
                ? 'false'
                : 'true'
        );


        tocToggle.setAttribute(
            'aria-label',
            CONFIG.desktopCollapsedByDefault
                ? 'Contentsを開く'
                : 'Contentsを折りたたむ'
        );


        tocHead.appendChild(
            tocTitle
        );

        tocHead.appendChild(
            tocToggle
        );


        var progress =
            document.createElement('div');

        progress.className =
            'ws-floating-toc-progress';


        var progressBar =
            document.createElement('div');

        progressBar.className =
            'ws-floating-toc-progress-bar';


        progress.appendChild(
            progressBar
        );


        var tocBody =
            document.createElement('div');

        tocBody.className =
            'ws-floating-toc-body';


        tocBody.appendChild(
            createTocList()
        );


        toc.appendChild(
            tocHead
        );

        toc.appendChild(
            progress
        );

        toc.appendChild(
            tocBody
        );


        document.body.appendChild(
            toc
        );


        /* =====================================================
           9. DESKTOP COLLAPSE
        ===================================================== */

        function setDesktopCollapsed(collapsed) {

            toc.classList.toggle(
                'is-collapsed',
                collapsed
            );


            tocToggle.setAttribute(
                'aria-expanded',
                collapsed
                    ? 'false'
                    : 'true'
            );


            tocToggle.setAttribute(
                'aria-label',
                collapsed
                    ? 'Contentsを開く'
                    : 'Contentsを折りたたむ'
            );

        }


        tocToggle.addEventListener(
            'click',
            function () {

                setDesktopCollapsed(
                    !toc.classList.contains(
                        'is-collapsed'
                    )
                );

            }
        );


        tocTitle.addEventListener(
            'click',
            function () {

                if (
                    toc.classList.contains(
                        'is-collapsed'
                    )
                ) {

                    setDesktopCollapsed(false);

                }

            }
        );


        /* =====================================================
           10. MOBILE TOC
        ===================================================== */

        var mobileTrigger =
            document.createElement('button');


        mobileTrigger.id =
            'ws-mobile-toc-trigger';

        mobileTrigger.type =
            'button';

        mobileTrigger.textContent =
            'Contents';


        mobileTrigger.setAttribute(
            'aria-label',
            '記事のContentsを開く'
        );

        mobileTrigger.setAttribute(
            'aria-expanded',
            'false'
        );


        var mobileMask =
            document.createElement('div');

        mobileMask.id =
            'ws-mobile-toc-mask';


        var mobileDrawer =
            document.createElement('aside');

        mobileDrawer.id =
            'ws-mobile-toc-drawer';


        mobileDrawer.setAttribute(
            'role',
            'dialog'
        );

        mobileDrawer.setAttribute(
            'aria-modal',
            'true'
        );

        mobileDrawer.setAttribute(
            'aria-label',
            '記事のContents'
        );


        var mobileHead =
            document.createElement('div');

        mobileHead.className =
            'ws-mobile-toc-drawer-head';


        var mobileTitle =
            document.createElement('strong');

        mobileTitle.textContent =
            'この記事のContents';


        var mobileClose =
            document.createElement('button');

        mobileClose.type =
            'button';

        mobileClose.className =
            'ws-mobile-toc-close';

        mobileClose.innerHTML =
            '&times;';


        mobileClose.setAttribute(
            'aria-label',
            'Contentsを閉じる'
        );


        mobileHead.appendChild(
            mobileTitle
        );

        mobileHead.appendChild(
            mobileClose
        );


        var mobileBody =
            document.createElement('div');

        mobileBody.className =
            'ws-mobile-toc-drawer-body';


        mobileBody.appendChild(
            createTocList()
        );


        mobileDrawer.appendChild(
            mobileHead
        );

        mobileDrawer.appendChild(
            mobileBody
        );


        document.body.appendChild(
            mobileMask
        );

        document.body.appendChild(
            mobileDrawer
        );

        document.body.appendChild(
            mobileTrigger
        );


        /* =====================================================
           11. MOBILE OPEN / CLOSE
        ===================================================== */

        function openMobileToc() {

            document.body.classList.add(
                'ws-toc-mobile-open'
            );


            mobileTrigger.setAttribute(
                'aria-expanded',
                'true'
            );

        }


        function closeMobileToc() {

            document.body.classList.remove(
                'ws-toc-mobile-open'
            );


            mobileTrigger.setAttribute(
                'aria-expanded',
                'false'
            );

        }


        mobileTrigger.addEventListener(
            'click',
            openMobileToc
        );


        mobileClose.addEventListener(
            'click',
            closeMobileToc
        );


        mobileMask.addEventListener(
            'click',
            closeMobileToc
        );


        document.addEventListener(
            'keydown',
            function (event) {

                if (event.key === 'Escape') {
                    closeMobileToc();
                }

            }
        );


        /* =====================================================
           12. HEADER OFFSET
        ===================================================== */

        function getHeaderOffset() {

            if (
                window.innerWidth <= 767
            ) {

                return CONFIG.headerOffsetMobile;

            }


            return CONFIG.headerOffsetDesktop;

        }


        /* =====================================================
           13. SMOOTH SCROLL
        ===================================================== */

        document.addEventListener(
            'click',
            function (event) {

                var link =
                    event.target.closest(
                        '.ws-floating-toc-link'
                    );


                if (!link) {
                    return;
                }


                var id =
                    link.dataset.target;


                var target =
                    document.getElementById(id);


                if (!target) {
                    return;
                }


                event.preventDefault();


                var targetY =
                    target
                        .getBoundingClientRect()
                        .top
                    +
                    window.pageYOffset
                    -
                    getHeaderOffset();


                window.scrollTo({
                    top: targetY,
                    behavior: 'smooth'
                });


                try {

                    history.replaceState(
                        null,
                        '',
                        '#' + id
                    );

                } catch (error) {}


                closeMobileToc();

            }
        );


        /* =====================================================
           14. ACTIVE SECTION
        ===================================================== */

        var lastActiveId =
            null;


        function updateActiveSection() {

            var offset =
                getHeaderOffset() + 35;


            var current =
                headings[0];


            headings.forEach(function (heading) {

                var top =
                    heading
                        .getBoundingClientRect()
                        .top;


                if (top <= offset) {

                    current =
                        heading;

                }

            });


            if (!current) {
                return;
            }


            var id =
                current.id;


            if (id === lastActiveId) {
                return;
            }


            lastActiveId =
                id;


            document
                .querySelectorAll(
                    '.ws-floating-toc-link'
                )
                .forEach(function (link) {

                    link.classList.toggle(
                        'is-active',
                        link.dataset.target === id
                    );

                });


            keepActiveLinkVisible(
                tocBody
            );


            keepActiveLinkVisible(
                mobileBody
            );

        }


        /* =====================================================
           15. KEEP ACTIVE LINK VISIBLE
        ===================================================== */

        function keepActiveLinkVisible(container) {

            if (!container) {
                return;
            }


            var active =
                container.querySelector(
                    '.ws-floating-toc-link.is-active'
                );


            if (!active) {
                return;
            }


            var top =
                active.offsetTop;


            var bottom =
                top + active.offsetHeight;


            var visibleTop =
                container.scrollTop;


            var visibleBottom =
                visibleTop +
                container.clientHeight;


            if (
                top < visibleTop + 12 ||
                bottom > visibleBottom - 12
            ) {

                var targetScroll =
                    Math.max(
                        0,
                        top -
                        container.clientHeight / 2
                    );


                container.scrollTo({
                    top: targetScroll,
                    behavior: 'smooth'
                });

            }

        }


        /* =====================================================
           16. READING PROGRESS
        ===================================================== */

        function updateProgress() {

            var rect =
                article.getBoundingClientRect();


            var articleTop =
                window.pageYOffset +
                rect.top;


            var articleHeight =
                article.offsetHeight;


            var current =
                window.pageYOffset +
                window.innerHeight * 0.25;


            var value =
                (
                    current - articleTop
                ) /
                articleHeight;


            value =
                Math.max(
                    0,
                    Math.min(
                        1,
                        value
                    )
                );


            progressBar.style.width =
                (value * 100) + '%';

        }


        /* =====================================================
           17. SCROLL / RESIZE PERFORMANCE
        ===================================================== */

        var ticking =
            false;


        function requestUpdate() {

            if (ticking) {
                return;
            }


            ticking =
                true;


            window.requestAnimationFrame(
                function () {

                    updateActiveSection();

                    updateProgress();

                    ticking =
                        false;

                }
            );

        }


        window.addEventListener(
            'scroll',
            requestUpdate,
            {
                passive: true
            }
        );


        window.addEventListener(
            'resize',
            requestUpdate,
            {
                passive: true
            }
        );


        /* =====================================================
           18. INITIAL STATE
        ===================================================== */

        updateActiveSection();

        updateProgress();

    });

})();
相关推荐
乐迪绘防伪油墨技术分享1 小时前
特种功能油墨选型指南:温变 / 遇水 / 荧光 / UV LED 技术对比与供应商参考
前端·html·uv
爱勇宝1 小时前
用了一个月 WorkBuddy,聊聊我的真实感受
前端·后端·程序员
愛芳芳1 小时前
基于 Electron + Vue3 的仿 PC 微信客户端项目实战
前端·javascript·css·elementui·typescript·electron·vue
你别说话了2 小时前
Vue实现高效拖拽效果 vue-Draggable
前端·vue.js
万物智能2 小时前
PWM散热风扇设置—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
前端·后端·算法
前端柱子2 小时前
q-floodfill白边锯齿?一招搞定抗锯齿边缘问题
前端
阿虎儿2 小时前
我的 HTML 样板(HTML Boilerplate)
前端·html
Amos_Web2 小时前
Rspack 源码解析(八):Module、Chunk 与 Runtime ID
前端·rust·源码阅读
彭于晏分晏2 小时前
深入理解 Vue 3 响应式原理:从 Proxy 到依赖收集
前端