前言 这份教程,记录了我为 AnZhiYu 主题添加横向卡片样式的全部过程。代码不多,但每一处都仔细调整过:斜切的视觉平衡、移动端的适配、分页后的稳定性……甚至强迫自己解决了偶数页布局错乱的 bug。
代码不是束缚,是自由的工具。愿你的博客,也能长出属于自己的姿态。
正文 文件路径分布
1 2 3 4 5 6 7 8 9 10 11 12 13 14 blog/ # Hexo 博客根目录 ├── themes/ │ └── anzhiyu/ # AnZhiYu 主题目录 │ ├── layout/ # 布局模板目录 │ │ ├── includes/ # 包含组件目录 │ │ │ └── mixins/ # mixins 组件目录 │ │ │ └── post-ui-horizontal.pug # ← 横向卡片 Pug 模板 │ │ └── index.pug # ← 首页布局文件(调用横向卡片) │ └── ... # 其他主题文件 ├── source/ # 源文件目录(会被复制到 public) │ └── cdn/ # CDN 资源目录 │ └── css/ │ └── horizontal-card.css # ← 横向卡片样式文件(你提供的 CSS) └── ... # 其他博客文件
新建post-ui-horizontal.pug
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 mixin postUIHorizontal() - let maxDate = 0 each item in site.posts.data if item.date > maxDate - maxDate = item.date .horizontal-post-list each article, index in page.posts.data - let link = article.link || article.path - let title = article.title || _p('no_title') - let post_cover = article.cover - let desc = article.description || article.excerpt || '' - let category = article.categories.data[0] - let dateStr = date(article.date, 'MM-DD') - let wc = wordcount(article.content) - const new_post = is_current('/') && (maxDate === article.date) // 判断左右位置:偶数左边,奇数右边 - let isLeft = index % 2 === 0 article.recent-post-item.horizontal-card(class=isLeft ? 'image-right' : 'image-left' onclick=`pjax.loadUrl('${url_for(link)}')`) // 图片在左(奇数卡片) if !isLeft && post_cover && theme.cover.index_enable .post-cover a(href=url_for(link) title=title) img.post_bg(src=url_for(post_cover) onerror=`this.onerror=null;this.src='`+ url_for(theme.error_img.post_page) + `'` alt=title loading="lazy") // 无图片占位(奇数卡片) if !isLeft && !(post_cover && theme.cover.index_enable) .post-cover.no-image a(href=url_for(link) title=title) .cover-placeholder // 内容区 .post-content .post-content-top .post-tips if (is_home() && (article.top || article.sticky > 0)) span.article-meta.sticky-warp i.anzhiyufont.anzhiyu-icon-thumbtack.sticky span.sticky= _p('sticky') a.post-title(href=url_for(link) title=title)= title .post-meta-wrap if (theme.post_meta.page.date_type) span.post-meta-date if (theme.post_meta.page.date_type === 'both') i.anzhiyufont.anzhiyu-icon-calendar-alt(style=`${theme.post_meta.page.date_format==="simple" ? "display:none":""}`) span.article-meta-label=_p('post.created') time.post-meta-date-created(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date) time=full_date(article.date))=date(article.date, config.date_format) span.article-meta-separator i.anzhiyufont.anzhiyu-icon-history(style=`font-size: 15px; ${theme.post_meta.page.date_format==="simple" ? "display:none":""}`) span.article-meta-label=_p('post.updated') time.post-meta-date-updated(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated) time=full_date(article.updated))=date(article.updated, config.date_format) else - let data_type_updated = theme.post_meta.page.date_type === 'updated' - let date_type = data_type_updated ? 'updated' : 'date' - let date_type_other = data_type_updated ? 'date' : 'updated' - let date_icon = data_type_updated ? 'anzhiyu-icon-history' :'anzhiyu-icon-calendar-days' - let date_title = data_type_updated ? _p('post.updated') : _p('post.created') - let date_title_other = data_type_updated ? _p('post.created') : _p('post.updated') i.anzhiyufont(class=date_icon style=`font-size: 15px; ${theme.post_meta.page.date_format==="simple" ? "display:none":""}`) span.article-meta-label=date_title time(datetime=date_xml(article[date_type]) title=date_title + ' ' + full_date(article[date_type]) time=full_date(article[date_type]))=date(article[date_type], config.date_format) time(datetime=date_xml(article[date_type_other]), class="time_hidden", title=date_title_other + ' ' + full_date(article[date_type_other]) time=full_date(article[date_type_other]))=date(article[date_type_other], config.date_format) if (theme.post_meta.page.tags && article.tags.data.length > 0) span.article-meta.tags each item, index in article.tags.data a(href=url_for(item.path) event.cancelbubble onclick="window.event.cancelBubble=true;").article-meta__tags span i.anzhiyufont.anzhiyu-icon-hashtag =item.name mixin countBlockHorizontal - needLoadCountJs = true span.article-meta span.article-meta-separator i.eucalyptus.icon-comments if block block span.article-meta-label= ' ' + _p('card_post_count') if theme.comments.card_post_count case theme.comments.use[0] when 'Valine' +countBlockHorizontal a(href=url_for(link) + '#post-comment') span.valine-comment-count(data-xid=url_for(link)) i.anzhiyufont.anzhiyu-icon-spinner.anzhiyu-spin when 'Waline' +countBlockHorizontal a(href=url_for(link) + '#post-comment') span.waline-comment-count(id=url_for(link)) i.anzhiyufont.anzhiyu-icon-spinner.anzhiyu-spin when 'Twikoo' +countBlockHorizontal a.twikoo-count(href=url_for(link) + '#post-comment' tabindex="-1") i.anzhiyufont.anzhiyu-icon-spinner.anzhiyu-spin when 'Artalk' +countBlockHorizontal a(href=url_for(link) + '#post-comment') span.artalk-count(data-page-key=url_for(link)) i.anzhiyufont.anzhiyu-icon-spinner.anzhiyu-spin case theme.index_post_content.method when false - break when 1 .post-desc!= article.description when 2 if article.description .post-desc!= article.description else - const content = strip_html(article.content) - let expert = content.substring(0, theme.index_post_content.length) - content.length > theme.index_post_content.length ? expert += ' ...' : '' .post-desc!= expert default - const content = strip_html(article.content) - let expert = content.substring(0, theme.index_post_content.length) - content.length > theme.index_post_content.length ? expert += ' ...' : '' .post-desc!= expert // 图片在右(偶数卡片) if isLeft && post_cover && theme.cover.index_enable .post-cover a(href=url_for(link) title=title) img.post_bg(src=url_for(post_cover) onerror=`this.onerror=null;this.src='`+ url_for(theme.error_img.post_page) + `'` alt=title loading="lazy") // 无图片占位(偶数卡片) if isLeft && !(post_cover && theme.cover.index_enable) .post-cover.no-image a(href=url_for(link) title=title) .cover-placeholder if theme.ad && theme.ad.index if (index + 1) % 3 == 0 .horizontal-card.ads-wrap!=theme.ad.index
修改index.pug
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 extends includes/layout.pug block content include ./includes/mixins/post-ui.pug // 关键:include 必须在顶层,不能放在 if 里面 include ./includes/mixins/post-ui-horizontal.pug #recent-posts.recent-posts include includes/categoryGroup.pug if theme.home_card_style === 'horizontal' +postUIHorizontal() else +postUI include includes/pagination.pug
新建horizontal-card.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 /* ========== 变量定义 ========== */ :root { --card-bg: #1a1a2e; --card-bg-alt: #16213e; --theme-color: #e94560; --skew-width: 60px; } /* ========== 基础卡片样式 ========== */ .horizontal-card { display: flex; flex-direction: row; width: 100%; height: 260px; border-radius: 16px; overflow: hidden; background: var(--card-bg); box-shadow: 0 8px 32px rgba(0,0,0,0.3); transition: all 0.4s ease; cursor: pointer; position: relative; margin-bottom: 32px; } .horizontal-card:hover { transform: translateY(-6px); box-shadow: 0 16px 48px rgba(233, 69, 96, 0.15); } /* ========== 内容区域基础 ========== */ .horizontal-card .post-content { width: calc(58% - 0px); flex: 0 0 calc(58% - 0px); max-width: calc(58% - 0px); padding: 32px 40px; display: flex; flex-direction: column; justify-content: space-between; position: relative; z-index: 2; min-width: 0; overflow: hidden; box-sizing: border-box; } .horizontal-card .post-title { font-size: 1.6rem; font-weight: 700; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 4px; word-break: break-word; overflow-wrap: break-word; } .horizontal-card .post-desc { font-size: 1rem; line-height: 1.8; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-top: 12px; word-break: break-word; overflow-wrap: break-word; } /* ========== 图片区域基础 ========== */ .horizontal-card .post-cover { width: calc(42% - 0px); flex: 0 0 calc(42% - 0px); max-width: calc(42% - 0px); min-width: 320px; height: 100%; position: relative; overflow: hidden; box-sizing: border-box; } .horizontal-card .post-cover img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.6s ease; } .horizontal-card:hover .post-cover img { transform: scale(1.1); } /* ========== 图片在右边 + 斜切线 ========== */ .horizontal-card.image-right { background: linear-gradient(135deg, var(--card-bg) 0%, var(--card-bg-alt) 100%); } .horizontal-card.image-right .post-content { order: 1; padding-right: 80px; clip-path: polygon( 0 0, calc(100% - var(--skew-width)) 0, 100% 100%, 0 100% ); } .horizontal-card.image-right .post-cover { order: 2; clip-path: polygon( var(--skew-width) 0, 100% 0, 100% 100%, 0 100% ); margin-left: calc(var(--skew-width) * -1); margin-right: calc(var(--skew-width) * -1); width: calc(42% + var(--skew-width)) !important; flex: 0 0 calc(42% + var(--skew-width)) !important; max-width: calc(42% + var(--skew-width)) !important; } /* ========== 图片在左边 + 斜切线 ========== */ .horizontal-card.image-left { background: linear-gradient(225deg, var(--card-bg) 0%, var(--card-bg-alt) 100%); } .horizontal-card.image-left .post-content { order: 2; padding-left: 80px; clip-path: polygon( var(--skew-width) 0, 100% 0, 100% 100%, 0 100% ); } .horizontal-card.image-left .post-cover { order: 1; clip-path: polygon( 0 0, calc(100% - var(--skew-width)) 0, 100% 100%, 0 100% ); margin-right: calc(var(--skew-width) * -1); margin-left: calc(var(--skew-width) * -1); width: calc(42% + var(--skew-width)) !important; flex: 0 0 calc(42% + var(--skew-width)) !important; max-width: calc(42% + var(--skew-width)) !important; } /* 第一张卡片距离顶部增加间距 */ .horizontal-card:first-child { margin-top: 32px; } /* ========== 响应式适配 ========== */ @media screen and (max-width: 768px) { .horizontal-card, .horizontal-card.image-left, .horizontal-card.image-right { flex-direction: column; height: auto; margin-bottom: 28px; } .horizontal-card .post-content, .horizontal-card.image-left .post-content, .horizontal-card.image-right .post-content { width: 100%; flex: none; max-width: 100%; padding: 24px; order: 2; clip-path: none; } .horizontal-card.image-left .post-cover, .horizontal-card.image-right .post-cover { width: 100% !important; flex: none !important; max-width: 100% !important; min-width: auto; height: 220px; order: 1; margin: 0 !important; clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%); } }
_config.anzhiyu.yml调用自定义horizontal-card.css
1 2 3 4 5 inject: head: # 自定义css ... - <link rel="stylesheet" href="/cdn/css/horizontal-card.css">
_config.anzhiyu.yml中使用新的文章卡片样式,建议添加article_double_row之后
1 2 3 4 5 6 7 8 9 ... # 首页双栏显示 article_double_row: true # 文章卡片样式: default | horizontal home_card_style: horizontal ...