更新记录
2022-03-31:内测版v0.08
2022-03-31:内测版v0.07
参考方向 | 教程原贴 |
---|---|
本帖的卡片原设为贰猹提供 | |
Flex布局参数解释 | |
Transition属性实现平滑过渡动画 | |
CSS伪类实现三角形绘制 |
在很久很久以前,到底有多久呢,我查了下聊天记录,
贰猹和我交流了下他对主题首页卡片的设计想法,效果很不错。原本呢,贰猹是打算等自己学好了前端就把这个卡片写出来的。然后,就这么咕咕咕了一个寒假(其实是被木木抓起来关进小黑屋里写友链朋友圈后端API,写不好不给饭吃)。冰老师在鸽之灵,看到后继有鸽,也会很欣慰吧。
然后我就忍不住了啊,这么优秀的设计,等贰猹写出来我要等到啥时候。二话不说,征求了贰猹的授权以后,咱就开始动手进行试做。
可以看到贰猹的设计里,有用到三角形的结构。然而在div,span这些传统的html元素中,是没有三角形的。最多通过调整border-radius做出圆形。所以这里就要用到border的特性了。
在传统的盒子布局中,border是占用盒子的位置内部位置的。所以,通过调整盒子四条边的border,同时把内部元素的长宽都设为0,就可以拼出三角形。缺点则是border没法设置background-image,没法直接把拼出的三角形当成相框来用。所以就需要用到z-index属性控制卡片层级,通过叠加拼接遮盖的方式,来实现形状的组合。 又因为relative和absolute定位下,元素的长宽属性的所谓100%对应的是不同等级的父级元素。(一个父级,一个祖父级?大概是这样子),总之我硬是用calc方法强行把伪类三角计算到了合适的位置。
编写手机端样式时,在F12界面调试伪类三角平移量的时候,突发奇想,可以用动画写个快门效果,所以最后的作品,手机端摒弃了贰猹原设里的倒三角描述卡片,转为类平行四边形的边框。同时加了悬停显示快门效果的遮罩动画。
我是打算先独享一个月再发布具体教程的,谁知道贰猹咕咕咕信誓旦旦的说给他一个月,早就把我的样式抠出来了。
那就让我们拭目以待吧。反正等他抠出来我再发布教程貌似也没啥问题。
2022 年 4 月 7 日更新。
在尝试找到界面曲面化来实现 3D 效果方案时,看到了 SAO 血条的 2D 实现方案,让我了解到一个新的 CSS 属性,clip-path,它可以通过 polygon 直接剪裁出多边形。非常的好用!而且还支持 animation。比如旧版方案里手机端的那个快门效果。 如果我用伪类实现,代码量是这样的:
然后若我用 clip-path 去实现,代码量是这样的:
差距啊!
我现在只觉得好多好多旧版方案都显得辣眼睛。要大改!侧栏的 SAO 卡片效果要改,直接画出好看的边框,首页的文章卡片要改,反正不能再搞不靠谱的三角形伪类拼接。还有以前写的 SAO 血条,啊,那个项目我是打算废弃了。换成新版友链方案怎么样,左边是一排血条,点击再右边显示好看的角色属性卡片这样子。用 clip-path 的话,血条那个形状已经完全不成问题了的说。哦,对了,还有 twikoo 评论的美化,聊天框这下子好整多了。调整好 margin 直接剪裁。flex 定位的 order 属性也可以用上去。这样自适应一定会好看许多。
好多好多项目要改,这就是技术负债啊。流下了没有技术的泪水。
预览效果
修改[Blogroot]\themes\butterfly\layout\includes\mixins\post-ui.pug
,将整个文件的内容替换为以下代码:
mixin postUI(posts)
each article , index in page.posts.data
.recent-post-item
-
let link = article.link || article.path
let title = article.title || _p('no_title')
const position = theme.cover.position
let leftOrRight = position === 'both'
? index%2 == 0 ? 'left' : 'right'
: position === 'left' ? 'left' : 'right'
let post_cover = article.cover
let no_cover = article.cover === false || !theme.cover.index_enable ? 'no-cover' : ''
-
.recent-post-content(class=leftOrRight)
a.article-content(href=url_for(link) title=subtitle)
//- Display the article introduction on homepage
case theme.index_post_content.method
when false
- break
when 1
.article-content-text!= article.description
when 2
if article.description
.article-content-text!= 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 += ' ...' : ''
.article-content-text!= 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 += ' ...' : ''
.article-content-text!= expert
.recent-post-info
a.article-title(href=url_for(link) title=subtitle)
.article-title-link= title
.recent-post-meta
.article-meta-wrap
if (is_home() && (article.top || article.sticky > 0))
span.article-meta
i.fas.fa-thumbtack.sticky
span.sticky= _p('sticky')
span.article-meta-separator |
if (theme.post_meta.page.date_type)
span.post-meta-date
if (theme.post_meta.page.date_type === 'both')
i.far.fa-calendar-alt
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))=date(article.date, config.date_format)
span.article-meta-separator |
i.fas.fa-history
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))=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_icon = data_type_updated ? 'fas fa-history' :'far fa-calendar-alt'
- let date_title = data_type_updated ? _p('post.updated') : _p('post.created')
i(class=date_icon)
span.article-meta-label=date_title
time(datetime=date_xml(article[date_type]) title=date_title + ' ' + full_date(article[date_type]))=date(article[date_type], config.date_format)
if (theme.post_meta.page.categories && article.categories.data.length > 0)
span.article-meta
span.article-meta-separator |
i.fas.fa-inbox
each item, index in article.categories.data
a(href=url_for(item.path)).article-meta__categories #[=item.name]
if (index < article.categories.data.length - 1)
i.fas.fa-angle-right.article-meta-link
if (theme.post_meta.page.tags && article.tags.data.length > 0)
span.article-meta.tags
span.article-meta-separator |
i.fas.fa-tag
each item, index in article.tags.data
a(href=url_for(item.path)).article-meta__tags #[=item.name]
if (index < article.tags.data.length - 1)
span.article-meta-link #[='•']
mixin countBlockInIndex
- needLoadCountJs = true
span.article-meta
span.article-meta-separator |
i.fas.fa-comments
if block
block
span.article-meta-label= ' ' + _p('card_post_count')
if theme.comments.card_post_count
case theme.comments.use[0]
when 'Disqus'
when 'Disqusjs'
+countBlockInIndex
a(href=full_url_for(link) + '#disqus_thread')
when 'Valine'
+countBlockInIndex
a(href=url_for(link) + '#post-comment' itemprop="discussionUrl")
span.valine-comment-count(data-xid=url_for(link) itemprop="commentCount")
when 'Waline'
+countBlockInIndex
a(href=url_for(link) + '#post-comment')
span.waline-comment-count(id=url_for(link))
when 'Twikoo'
+countBlockInIndex
a.twikoo-count(href=url_for(link) + '#post-comment')
when 'Facebook Comments'
+countBlockInIndex
a(href=url_for(link) + '#post-comment')
span.fb-comments-count(data-href=urlNoIndex(article.permalink))
.recent-post-cover
img.article-cover(src=url_for(post_cover) onerror=`this.onerror=null;this.src='`+ url_for(theme.error_img.post_page) + `'` alt=subtitle)
if theme.ad && theme.ad.index
if (index + 1) % 3 == 0
.recent-post-item.ads-wrap!=theme.ad.index
[Blogroot]\themes\butterfly\source\css\_page\homepage.styl
,将整个文件的内容替换为以下代码://default color:
:root
--recent-post-bgcolor: rgba(255, 255, 255, 0.9)
--article-content-bgcolor: #49b1f5
--recent-post-triangle: #fff
--recent-post-cover-shadow: #ffffff
[data-theme="dark"]
--recent-post-bgcolor: rgba(35,35,35,0.5)
--article-content-bgcolor: #99999a
--recent-post-triangle: #37e2dd
--recent-post-cover-shadow: #232323
.recent-posts
padding 0 15px 0 15px
.recent-post-item
margin-bottom 15px
width 100%
background var(--recent-post-bgcolor)
overflow hidden
border-radius 15px
.recent-post-info
.article-title-link
display -webkit-box
-webkit-box-orient vertical
-webkit-line-clamp 2
overflow hidden
.article-content
background var(--article-content-bgcolor)
position relative
display flex
align-items: center;
justify-content: center;
.article-content-text
transition: all .8s cubic-bezier(0.59, 0.01, 0.48, 1.17)
display -webkit-box
-webkit-box-orient vertical
-webkit-line-clamp 4
text-overflow: ellipsis
overflow hidden
color #fff
text-shadow: 1px 2px 3px #000;
.recent-post-cover
position relative
background transparent
img
&.article-cover
height 100%
width 100%
object-fit cover
.recent-post-info
align-items center
flex-direction column
position relative
background var(--recent-post-bgcolor)
display flex
color #000000
.article-title
height 50%
font-size 24px
display: flex
align-items: center
justify-content: flex-end
flex-direction: column
.article-title-link
color: var(--text-highlight-color)
transition: all .2s ease-in-out
&:hover
color: $text-hover
.recent-post-meta
height 50%
display: flex
align-items: center
justify-content: flex-start
flex-direction: column
.article-meta-wrap
font-size 12px
color #969797
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
a
color: var(--text-highlight-color)
transition: all .2s ease-in-out
color #969797
&:hover
color: $text-hover
&.ads-wrap
display: block !important
height: auto !important
@media screen and (min-width:600px)
.recent-post-item
&:hover
.recent-post-content
&.both,
&.right
transform translateX(21%)
transition: all .8s cubic-bezier(0.59, 0.01, 0.48, 1.17)
&::before
transition: all .8s cubic-bezier(0.59, 0.01, 0.48, 1.17)
left: 50px;
.article-content-text
margin 20px 20px 20px 60px
&.left
transform translateX(-21%)
transition: all .8s cubic-bezier(0.59, 0.01, 0.48, 1.17)
&::before
transition: all .8s cubic-bezier(0.59, 0.01, 0.48, 1.17)
right: 50px;
.article-content-text
margin 20px 60px 20px 20px
.recent-post-content
background var(--recent-post-bgcolor)
position relative
height 200px
width 130%
z-index 0
display flex
overflow hidden
border 0px solid
&::before
content: "";
width: 0;
height: 0;
background: transparent;
position: absolute;
z-index: 3;
top: calc(50% - 10px);
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
transition: all .5s cubic-bezier(0.59, 0.01, 0.48, 1.17)
&.both,
&.right
flex-direction: row;
left calc(-23.07% - 41px)
transition: all .5s cubic-bezier(0.59, 0.01, 0.48, 1.17)
&::before
left: calc(23.07% + 40px);
border-left: 6px solid var(--recent-post-triangle);
.recent-post-info
&::before
background linear-gradient(to right, var(--recent-post-cover-shadow), transparent)
left calc(100% - 1px)
.article-content
&::before
right -59px
border-left 60px solid var(--article-content-bgcolor)
.article-content-text
margin 20px 20px 20px 0px
.article-title
padding 0px 30px 0px 70px
.recent-post-meta
padding 0px 20px 0px 70px
&.left
flex-direction: row-reverse;
right 9px
transition: all .5s cubic-bezier(0.59, 0.01, 0.48, 1.17)
&::before
right: calc(23.07% + 40px);
border-right: 6px solid var(--recent-post-triangle);
.recent-post-info
&::before
background linear-gradient(to left, var(--recent-post-cover-shadow), transparent)
right calc(100% - 1px)
.article-content
&::before
left -59px
border-right 60px solid var(--article-content-bgcolor)
.article-content-text
margin 20px 0px 20px 20px
.article-title
padding 0px 70px 0px 30px
.recent-post-meta
padding 0px 70px 0px 20px
.article-content
width 30%
height 200px
left 0
align-items center
&::before
content ""
width 0
height 0
background transparent
position absolute
z-index 2
top 0
border-top 100px solid transparent
border-bottom 100px solid transparent
.recent-post-info
width 60%
height 200px
&::before
content ""
width 200px
height 200px
position absolute
z-index 1
top 0
.recent-post-meta
& > .article-meta-wrap
margin: 6px 0
color: $theme-meta-color
font-size: 90%
& > .post-meta-date
cursor: default
.sticky
color: $sticky-color
i
margin: 0 4px 0 0
.article-meta-label
if hexo-config('post_meta.page.label')
padding-right: 4px
else
display: none
.article-meta-separator
margin: 0 6px
.article-meta-link
margin: 0 4px
if hexo-config('post_meta.page.date_format') == 'relative'
time
display: none
a
color: $theme-meta-color
&:hover
color: $text-hover
text-decoration: underline
.recent-post-cover
width 40%
height 200px
@media screen and (max-width:600px)
.recent-post-item
height 400px
.recent-post-content
display flex
flex-direction: column
height 400px
.article-content
pointer-events none
order: 1;
height: 200px;
position: absolute;
width: calc(100% - 40px);
z-index: 3;
background: rgba(22,22,22,0.5);
border-top-left-radius: 15px;
border-top-right-radius: 15px;
display: none
opacity: 0
.article-content-text
height 120px
color: white;
width: 80%
.recent-post-cover
order: 2
height 200px
transition: all .5s
.recent-post-info
order: 3
height 200px
&::before
content: '';
width: 0;
height: 0;
position: absolute;
z-index: 3;
bottom: calc(100% - 4px);
left: 0;
border-bottom: 50px solid var(--recent-post-bgcolor);
border-right: 300px solid transparent;
&::after
content: '';
width: 0;
height: 0;
position: absolute;
z-index: 3;
bottom: calc(100% + 150px);
right: 0;
border-top: 50px solid var(--recent-post-bgcolor);
border-left: 300px solid transparent;
.article-title
padding: 0px 35px 0px 35px
.recent-post-meta
padding: 0px 30px 0px 30px
&:hover
.article-content
display: flex !important;
animation: shutter-effect-content 0.5s 2 forwards linear
.recent-post-info
&::before
animation: shutter-effect-left 0.5s 1 ease-in-out
&::after
animation: shutter-effect-right 0.5s 1 ease-in-out
.recent-post-cover
filter blur(2px)
@keyframes shutter-effect-right {
0%{
bottom: calc(100% + 150px);
border-top: 50px solid var(--recent-post-bgcolor);
border-left: 300px solid transparent;
}
50%{
bottom: 100%;
border-top: 200px solid var(--recent-post-bgcolor);
border-left: 600px solid transparent;
}
100%{
bottom: calc(100% + 150px);
border-top: 50px solid var(--recent-post-bgcolor);
border-left: 300px solid transparent;
}
}
@keyframes shutter-effect-left {
0%{
bottom: calc(100% - 4px);
border-bottom: 50px solid var(--recent-post-bgcolor);
border-right: 300px solid transparent;
}
50%{
bottom: calc(100% - 4px);
border-bottom: 200px solid var(--recent-post-bgcolor);
border-right: 600px solid transparent;
}
100%{
bottom: calc(100% - 4px);
border-bottom: 50px solid var(--recent-post-bgcolor);
border-right: 300px solid transparent;
}
}
@keyframes shutter-effect-content {
from {
opacity: 0
}
to {
opacity: 1
}
}
注意事项
样式配色因为采用了大量伪类,所以如果底色采用了半透明配色,可能因为卡片叠加加深导致接合边界非常明显的暴露出来。所以在配色上,我是不建议加半透明的。
因为部分伪类的偏移量是靠计算得出的,为了尽量满足自适应效果,部分位置保留了5%左右的容差。所以在一些极端屏宽比下,还是会出现一些样式不完美问题。(处女座不能用可真是遗憾呢)