[js][css] 分享一个类语雀代码块折叠 JS

本贴最后更新于 285 天前,其中的信息可能已经时移世易

image.png

image.png

PixPin20250312155832.gif

.code-block {
  position: relative;
  margin: 1.5rem 0;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: all 0.3s ease;
  overflow: hidden;
  z-index: 1;
  background-color: #f5f5f5;
  padding: 1rem;
  border: 2px solid transparent;
}

@keyframes neon-border {
  0% { border-color: #ff0080; box-shadow: 0 0 5px #ff0080; }
  25% { border-color: #00ffff; box-shadow: 0 0 5px #00ffff; }
  50% { border-color: #00ff00; box-shadow: 0 0 5px #00ff00; }
  75% { border-color: #ffff00; box-shadow: 0 0 5px #ffff00; }
  100% { border-color: #ff0080; box-shadow: 0 0 5px #ff0080; }
}

.code-block:hover:not([data-fullscreen="true"]) {
  animation: neon-border 5s infinite;
  transform: translateY(-3px) scale(1.02);
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
  z-index: 10;
}

.code-block pre {
  margin: 0;
  padding: 0;
}

.code-block-header .code-block-title {
  font-weight: 500;
}

.theme-dark .code-block {
  background-color: #1e1e1e;
  color: #d4d4d4;
}

.code-block .protyle-action {
  opacity: 0;
  transition: opacity 0.3s ease;
}

.code-block:hover .protyle-action {
  opacity: 1;
}

.code-block[data-fullscreen="true"] {
  border: none;
  animation: none;
  transform: none !important;
  box-shadow: none;
  border-radius: 0;
  margin: 0;
  padding: 0;
}

.code-block[data-fullscreen="true"] .hljs {
  height: calc(100vh - 45px);
  padding: 1rem;
  box-sizing: border-box;
}

.code-block[data-fullscreen="true"] .code-block-header {
  padding: 8px 15px;
  background-color: rgba(0, 0, 0, 0.1);
  border-bottom: 1px solid rgba(127, 127, 127, 0.3);
}

.code-block[data-fullscreen="true"] .code-block-header {
  border-left: 4px solid #ff0080;
  transition: border-color 0.5s ease;
}

.code-block[data-fullscreen="true"] .code-block-header:hover {
  border-left-color: #00ffff;
}

.code-fullscreen-button {
  opacity: 0.6;
  transition: transform 0.3s ease, opacity 0.3s ease;
}

.code-fullscreen-button:hover {
  opacity: 1;
  transform: rotate(90deg);
}


.code-block.animate .hljs {
  transition: max-height 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

.code-arrow {
  transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}


@media (max-width: 768px) {
  .code-block:hover:not([data-fullscreen="true"]) {
    transform: translateY(-2px) scale(1.01);
  }
  
  .code-fullscreen-button {
    right: 20px;
  }
}
(() => {
    const config = {
        defaultTitle: "Code",
        arrowExpandedHTML: '<svg class="code-arrow" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M8.59 16.59L13.17 12L8.59 7.41L10 6L16 12L10 18L8.59 16.59Z"></path></svg>',
        arrowCollapsedHTML: '<svg class="code-arrow" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M8.59 16.59L13.17 12L8.59 7.41L10 6L16 12L10 18L8.59 16.59Z"></path></svg>',
        fullscreenHTML: '<svg class="code-fullscreen" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"></path></svg>',
        exitFullscreenHTML: '<svg class="code-fullscreen" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"></path></svg>',
        headerMinWidth: 100,
        collapseAnimation: true,
        animationDuration: 300,
    };
    function addStyles() {
        const styles = `
            .code-block-header {
                display: flex;
                align-items: center;
                padding: 4px 8px;
                cursor: pointer;
                background-color: var(--b3-code-block-bg);
                color: var(--b3-protyle-inline-code-color);
                border-bottom: 1px solid rgba(127, 127, 127, 0.2);
                font-size: 13px;
                font-family: var(--b3-font-family-code);
                position: relative;
            }
  
            .code-block-title {
                margin-left: 8px;
                flex-grow: 1;
                line-height: 1.5;
                overflow: hidden;
                white-space: nowrap;
                text-overflow: ellipsis;
            }
  
            .code-arrow {
                width: 35px;
                height: 35px;
                margin-left: 20px;
                transition: transform 0.2s ease;
            }
  
            .code-fullscreen-button {
                position: absolute;
                right: 50px;
                width: 35px;
                height: 35px;
                display: flex;
                align-items: center;
                justify-content: center;
                cursor: pointer;
                opacity: 0.7;
                transition: all 0.3s ease;
            }
  
            .code-fullscreen-button:hover {
                opacity: 1;
                transform: scale(1.1);
            }
  
            .code-fullscreen {
                width: 24px;
                height: 24px;
            }
  
            .code-block-title:empty:before {
                content: "${config.defaultTitle}";
                opacity: 0.6;
            }
  
            .code-block[data-collapsed="true"] .code-block-header .code-arrow {
                transform: rotate(0deg);
            }
  
            .code-block[data-collapsed="false"] .code-block-header .code-arrow {
                transform: rotate(90deg);
            }
  
            .code-block[data-collapsed="true"] .code-block-header {
                border-bottom: none;
                opacity: 0.8;
            }
  
            .code-block-title:focus {
                outline: none;
                border-bottom: 1px dotted var(--b3-protyle-inline-code-color);
            }
  
            .code-block[data-collapsed="true"] .hljs {
                display: none;
            }
  
            .code-block[data-fullscreen="true"] {
                position: fixed;
                top: 0;
                left: 0;
                width: 100vw;
                height: 100vh;
                z-index: 9999;
                background-color: var(--b3-code-block-bg);
                display: flex;
                flex-direction: column;
                overflow: hidden;
            }
  
            .code-block[data-fullscreen="true"] .hljs {
                flex-grow: 1;
                overflow: auto;
                max-height: none !important;
                display: block !important;
                height: auto !important;
                opacity: 1 !important;
            }
  
            .code-block[data-fullscreen="true"] .code-block-header {
                position: sticky;
                top: 0;
                z-index: 1;
            }
        `;
        const styleEl = document.createElement('style');
        styleEl.innerHTML = styles;
        document.head.appendChild(styleEl);
    }
    function createCodeBlockHeader(codeBlock) {
        if (codeBlock.querySelector('.code-block-header')) {
            return;
        }
        const header = document.createElement('div');
        header.className = 'code-block-header protyle-custom';
  
        header.innerHTML = config.arrowExpandedHTML;
        const title = document.createElement('div');
        title.className = 'code-block-title';
        title.contentEditable = 'true';
        title.spellcheck = false;
  
        const language = getCodeBlockLanguage(codeBlock);
        if (language) {
            title.textContent = language;
        }
  
        header.appendChild(title);
  
        const fullscreenButton = document.createElement('div');
        fullscreenButton.className = 'code-fullscreen-button';
        fullscreenButton.innerHTML = config.fullscreenHTML;
        fullscreenButton.title = "全屏显示代码";
  
        header.appendChild(fullscreenButton);
  
        header.addEventListener('click', (e) => {
            if ((e.target === title && document.activeElement === title) || 
                fullscreenButton.contains(e.target)) {
                return;
            }
            toggleCodeBlock(codeBlock);
        });
  
        title.addEventListener('click', (e) => {
            e.stopPropagation();
        });
  
        title.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                title.blur();
            }
        });
  
        fullscreenButton.addEventListener('click', (e) => {
            e.stopPropagation();
            toggleFullscreen(codeBlock, fullscreenButton);
        });
  
        codeBlock.insertBefore(header, codeBlock.firstChild);
  
        codeBlock.setAttribute('data-collapsed', 'false');
        codeBlock.setAttribute('data-fullscreen', 'false');
    }
  
    function toggleFullscreen(codeBlock, button) {
        const isFullscreen = codeBlock.getAttribute('data-fullscreen') === 'true';
        const hljs = codeBlock.querySelector('.hljs');
  
        if (isFullscreen) {
            codeBlock.setAttribute('data-fullscreen', 'false');
            button.innerHTML = config.fullscreenHTML;
            button.title = "全屏显示代码";
  
      
            if (codeBlock._wasCollapsedBeforeFullscreen) {
           
                if (hljs) {
                    hljs.style.display = '';
                    hljs.style.maxHeight = '';
                    hljs.style.height = '';
                    hljs.style.opacity = '';
                    hljs.style.overflow = '';
    
             
                    delete hljs.dataset.animating;
                }
  
        
                setTimeout(() => {
                    codeBlock.setAttribute('data-collapsed', 'true');
                    if (hljs) {
                        hljs.style.display = 'none';
                    }
                    delete codeBlock._wasCollapsedBeforeFullscreen;
                }, 50);
            }
  
            if (window._scrollPositionBeforeFullscreen !== undefined) {
                window.scrollTo(0, window._scrollPositionBeforeFullscreen);
                delete window._scrollPositionBeforeFullscreen;
            }
        } else {
            window._scrollPositionBeforeFullscreen = window.scrollY;
  
            if (codeBlock.getAttribute('data-collapsed') === 'true') {
                codeBlock._wasCollapsedBeforeFullscreen = true;
                codeBlock.setAttribute('data-collapsed', 'false');
  
                if (hljs) {
         
                    hljs.style.display = 'block';
                    hljs.style.maxHeight = 'none';
                    hljs.style.height = 'auto';
                    hljs.style.opacity = '1';
                    hljs.style.overflow = 'auto';
    
        
                    if (hljs.dataset.animating === 'true') {
                        delete hljs.dataset.animating;
                    }
                }
            }
  
            codeBlock.setAttribute('data-fullscreen', 'true');
            button.innerHTML = config.exitFullscreenHTML;
            button.title = "退出全屏";
  
            setTimeout(() => {
                if (hljs) {
                    hljs.scrollTop = 0;
                }
            }, 0);
        }
    }
  
    function getCodeBlockLanguage(codeBlock) {
        const hljsElement = codeBlock.querySelector('.hljs');
        if (!hljsElement) return null;
  
        const classes = hljsElement.className.split(' ');
        for (const cls of classes) {
            if (cls !== 'hljs' && cls.length > 0) {
                return cls;
            }
        }
  
        return null;
    }
    function toggleCodeBlock(codeBlock) {
        if (codeBlock.getAttribute('data-fullscreen') === 'true') {
            return;
        }
  
        const isCollapsed = codeBlock.getAttribute('data-collapsed') === 'true';
        const hljs = codeBlock.querySelector('.hljs');
  
        if (!hljs) return;
  
        if (hljs.dataset.animating === 'true') {
            return;
        }
  
        hljs.dataset.animating = 'true';
  
        hljs.style.transition = 'none';
        codeBlock.classList.remove('animate');
  
        if (isCollapsed) {
            hljs.style.display = 'block';
            hljs.style.height = '0px';
            hljs.style.overflow = 'hidden';
            hljs.style.opacity = '0';
  
            const targetHeight = hljs.scrollHeight;
  
            const heightAnimation = hljs.animate([
                { height: '0px', opacity: 0 },
                { height: targetHeight + 'px', opacity: 1 }
            ], {
                duration: 250,
                easing: 'cubic-bezier(0.4, 0.0, 0.2, 1)',
                fill: 'forwards'
            });
  
            heightAnimation.onfinish = () => {
                hljs.style.height = '';
                hljs.style.overflow = '';
                hljs.style.opacity = '';
                hljs.style.transition = '';
  
                delete hljs.dataset.animating;
            };
        } else {
            const startHeight = hljs.offsetHeight;
            hljs.style.height = startHeight + 'px';
            hljs.style.overflow = 'hidden';
  
            const opacityAnimation = hljs.animate([
                { opacity: 1 },
                { opacity: 0 }
            ], {
                duration: 120,
                easing: 'ease-out',
                fill: 'forwards'
            });
  
            setTimeout(() => {
                const heightAnimation = hljs.animate([
                    { height: startHeight + 'px' },
                    { height: '0px' }
                ], {
                    duration: 180, 
                    easing: 'cubic-bezier(0.4, 0.0, 0.2, 1)',
                    fill: 'forwards'
                });
  
                heightAnimation.onfinish = () => {
                    hljs.style.display = 'none';
    
                    hljs.style.height = '';
                    hljs.style.opacity = '';
                    hljs.style.overflow = '';
                    hljs.style.transition = '';
    
                    delete hljs.dataset.animating;
                };
            }, 70);
        }
  
        codeBlock.setAttribute('data-collapsed', !isCollapsed);
    }
    function processCodeBlocks(element) {
        const codeBlocks = element.querySelectorAll('.code-block:not([data-processed="true"])');
        codeBlocks.forEach(codeBlock => {
            createCodeBlockHeader(codeBlock);
            codeBlock.setAttribute('data-processed', 'true');
        });
    }
    function observeCodeBlockAddition(container) {
        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.classList && node.classList.contains('code-block')) {
                                createCodeBlockHeader(node);
                                node.setAttribute('data-processed', 'true');
                            }
      
                            if (node.querySelectorAll) {
                                processCodeBlocks(node);
                            }
                        }
                    });
                }
            });
        });
  
        observer.observe(container, { 
            childList: true, 
            subtree: true 
        });
  
        return observer;
    }
    function setupKeyboardHandlers() {
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') {
                const fullscreenCodeBlock = document.querySelector('.code-block[data-fullscreen="true"]');
                if (fullscreenCodeBlock) {
                    const fullscreenButton = fullscreenCodeBlock.querySelector('.code-fullscreen-button');
                    if (fullscreenButton) {
                        toggleFullscreen(fullscreenCodeBlock, fullscreenButton);
                        e.preventDefault();
                    }
                }
            }
        });
    }
    function waitForElement(selector) {
        return new Promise(resolve => {
            if (document.querySelector(selector)) {
                return resolve(document.querySelector(selector));
            }
  
            const observer = new MutationObserver(() => {
                if (document.querySelector(selector)) {
                    observer.disconnect();
                    resolve(document.querySelector(selector));
                }
            });
  
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        });
    }
    function isMobile() {
        return !!document.getElementById("sidebar");
    }
    async function init() {
        addStyles();
  
        setupKeyboardHandlers();
  
        const container = await waitForElement(isMobile() ? '.protyle-content' : '.layout__center');
  
        processCodeBlocks(container);
  
        observeProtyleAddition(container, protyles => {
            protyles.forEach(protyle => {
                if (!protyle.classList.contains('protyle')) {
                    protyle = protyle.closest('.protyle');
                }
  
                if (protyle) {
                    processCodeBlocks(protyle);
                    observeCodeBlockAddition(protyle);
                }
            });
        });
    }
    function observeProtyleAddition(container, callback) {
        const observer = new MutationObserver(mutations => {
            const protyles = [];
  
            mutations.forEach(mutation => {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.classList && 
                                (node.classList.contains('protyle') || 
                                 node.classList.contains('protyle-content'))) {
                                protyles.push(node);
                            }
                        }
                    });
                }
            });
  
            if (protyles.length > 0) {
                callback(protyles);
            }
        });
  
        observer.observe(container, { 
            childList: true, 
            subtree: true 
        });
  
        return observer;
    }
    init();
})();
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    28452 引用 • 119811 回帖 • 1 关注
  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    286 引用 • 1988 回帖
4 操作
Luuxcyzz 在 2025-03-12 16:02:38 更新了该帖
Luuxcyzz 在 2025-03-12 15:48:47 更新了该帖
JeffreyChen 在 2025-03-12 12:08:18 更新了该帖
JeffreyChen 在 2025-03-12 12:07:46 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • wilsons 1 赞同

    我也分享一个折叠代码块的 [js] 折叠代码块

    image.png

  • 其他回帖
  • Luuxcyzz 1 赞同
    作者

    更新过了 再看看

  • jacob111

    看起来楼主的代码里是有这部分的,但是没生效

    1 回复
  • jacob111

    试了下很不错呀,唯一一点是折叠的时候和展开的时候 code 左边的符号是不是应该有些区别。

    参考了下 confluence,折叠块折叠起来的时候如果有更明显的标志知道这个块是折叠的可能会比较好。

    1 回复
  • 查看全部回帖

推荐标签 标签

  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 123 关注
  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 57 关注
  • 游戏

    沉迷游戏伤身,强撸灰飞烟灭。

    188 引用 • 833 回帖
  • Scala

    Scala 是一门多范式的编程语言,集成面向对象编程和函数式编程的各种特性。

    13 引用 • 11 回帖 • 181 关注
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    89 引用 • 1251 回帖 • 376 关注
  • 小说

    小说是以刻画人物形象为中心,通过完整的故事情节和环境描写来反映社会生活的文学体裁。

    33 引用 • 108 回帖
  • danl
    216 关注
  • Excel
    32 引用 • 29 回帖 • 1 关注
  • Windows

    Microsoft Windows 是美国微软公司研发的一套操作系统,它问世于 1985 年,起初仅仅是 Microsoft-DOS 模拟环境,后续的系统版本由于微软不断的更新升级,不但易用,也慢慢的成为家家户户人们最喜爱的操作系统。

    232 引用 • 484 回帖
  • 锤子科技

    锤子科技(Smartisan)成立于 2012 年 5 月,是一家制造移动互联网终端设备的公司,公司的使命是用完美主义的工匠精神,打造用户体验一流的数码消费类产品(智能手机为主),改善人们的生活质量。

    4 引用 • 31 回帖 • 3 关注
  • 自由行
  • Elasticsearch

    Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful 接口。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

    117 引用 • 99 回帖 • 190 关注
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 692 关注
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    34 引用 • 333 回帖 • 1 关注
  • Q&A

    提问之前请先看《提问的智慧》,好的问题比好的答案更有价值。

    11158 引用 • 50674 回帖 • 50 关注
  • TGIF

    Thank God It's Friday! 感谢老天,总算到星期五啦!

    293 引用 • 4496 回帖 • 687 关注
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 668 关注
  • Gitea

    Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证。

    5 引用 • 16 回帖 • 2 关注
  • SendCloud

    SendCloud 由搜狐武汉研发中心孵化的项目,是致力于为开发者提供高质量的触发邮件服务的云端邮件发送平台,为开发者提供便利的 API 接口来调用服务,让邮件准确迅速到达用户收件箱并获得强大的追踪数据。

    2 引用 • 8 回帖 • 545 关注
  • 创业

    你比 99% 的人都优秀么?

    81 引用 • 1396 回帖
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3206 引用 • 8217 回帖
  • Firefox

    Mozilla Firefox 中文俗称“火狐”(正式缩写为 Fx 或 fx,非正式缩写为 FF),是一个开源的网页浏览器,使用 Gecko 排版引擎,支持多种操作系统,如 Windows、OSX 及 Linux 等。

    7 引用 • 30 回帖 • 368 关注
  • BookxNote

    BookxNote 是一款全新的电子书学习工具,助力您的学习与思考,让您的大脑更高效的记忆。

    笔记整理交给我,一心只读圣贤书。

    1 引用 • 1 回帖 • 1 关注
  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    960 引用 • 946 回帖
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    59 引用 • 25 回帖 • 4 关注
  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    45 引用 • 44 回帖 • 2 关注
  • OnlyOffice
    4 引用 • 41 关注