大家好,我是 Steven。
今集我们会模仿 App Store 这个卡片展开效果:
本来想只用 HTML 和 CSS 去制作,但最后都要运用到 JavaScript,那我们立即开始吧。
HTML 的部分
打开 CodePen 编辑器,首先建立 HTML 结构。新增一个 <div>,class 是 card,里面新增图片,图片来源使用 Unsplash 网站的随机图片。再加入标题,文字例如是 APP OF THE DAY。
然后新增一个 <div>,class 是 content-wrapper,里面再新增一个 <div>,class 是 content。为什么需要两层 <div> 呢,我们稍后会介绍。
里面新增内文,生成一些假字,然后用 <p> 这个段落标签整理好它们。
HTML 结构大致上是这样,然后到 CSS 的部份。
CSS 的部分
新增 :root 选择器,设定基础文字大小为 15px。字体 font-family 设定为 Helvetica。
然后定义一些变量,由于这个效果适合在手机的宽度上展示,所以我会将网页宽度设定为 480px,定义 --body-width 是 480px。然后定义卡片的大小,--card-width 定义为 420px,--card-height 定义为 280px。
再定义图片的高度,未展开的图片高度 --img-height 定义为 226px,展开后的图片高度 --img-height-expanded 定义为 320px。最后,背景颜色设定为深灰色。
加入 body 选择器,设定宽度为 var(--body-width),背景颜色设定为浅灰色,margin 设定为 auto,这样就会将 body 置中。
然后将内容左右置中,设定 display 是 flex,flex-direction 设定为 column,align-items 设定为 center,然后 min-height 设定为 100vh。最顶和最底加一点 padding,设定为 1rem 0。
加入 .card 选择器,宽度设定为 var(--card-width),高度设定为 var(--card-height),背景颜色设定为白色。
暂时看不到白色的背景,是因为这张图片的大小比起 card 容器更大完全遮盖了。新增 .card img 选择器,将 display 设定为 block,宽度设定为 100%,然后高度设定为 var(--img-height),这时候会发现图片的比例不对,加入 object-fit: cover 这个设定就可以让图片按比例缩放填满。
回到 .card 的设定,加入圆角,border-radius 设定为 1rem。再加点阴影,颜色是非常浅的灰色就可以了。
然后会发现图片的左上角和右上角并没有圆角设定,我们可以在 .card 中加入 overflow: hidden,让 .card 将超出的部份遮盖,将图片变成圆角。不过由于在打开 .card 的时候,圆角会变为直角,而 overflow 并不能套用动画的,所以在这里不会用上 overflow,而是在 img 中直接将左上角和右上角套用圆角设定。
然后设定标题字的样式,加入 .card h4 选择器,将 margin 设定为 0,文字大小设定为 1.5rem;字体设定为粗体;加入 padding,上下设定为 0.8rem,左右设定为 1.2rem;背景颜色设定为白色。
我想文字在垂直方向更加置中,设定 line-height 为 2rem,然后将字距缩小一点,letter-spacing 设定为 -0.5px,再将 padding-bottom 设定为 0,就不会遮着 .card 的圆角了。
接下来是设定内文的部份,分别加入 .card .content-wrapper 与 .card .content-wrapper .content 选择器。
为什么我们会分为 .content-wrapper 和 .content 呢?因为当点击卡片展开的时候,会通过 JavaScript 计算并设定内文容器的高度,所以我们会将展开的动画套用到外层,而高度的设定就会套用到内层。
这样当关闭卡片的时候,就不用再将高度的设定还原,其实重点是可以少写一些 JavaScript,让 CSS 去完成它。
先设定 .content 的样式,加入 padding,设定为上下 0,左右 1.2rem;背景颜色设定为白色;然后 overflow 设定为 auto,这样当内文高于容器所设定的高度时,页面就可以卷动。而容器的高度会在稍后 JavaScript 那边控制。
内文段落的样式也调整一下,加入 .card p 选择器,将文字大小设定为 1.2rem,行高设定为 1.5rem 就可以了。
然后到 .content-wrapper,设定高度是 0,再加入 overflow: hidden,这样超出容器的部份就会隐藏。
现在卡片的样式就差不多了,在开始做展开功能之前,先在 HTML 中再复制多几个卡片。为了展示不同的随机图片,我会在 Unsplash 的网址中加入不同的参数,在后面加上 1, 2, 3, 4,就可以了。
然后到 .card 选择器上,加入 margin: 1rem 0 将卡片分开一点。
JavaScript 的部分
接下来处理卡片展开的部份,我会通过在点击卡片时,加入一个 class 去改变它的样式,从而达到展开的效果。为了减少编写 JavaScript 的数量,我会加入 jQuery,打开 Settings,在 JS 的部份加入 jQuery,然后点击 Save & Close。
在 JavaScript 的部份,加入 .card 的 on('click') 事件。定义一个变量,card 赋值 e.currentTarget,这里获取到的就是所点击的 .card 的本身。
然后加入 $(card).toggleClass('active'),这样当 card 没有 active class 的时候就会加上,有的时候就会移除,达到展开与收起的效果。
CSS 样式的调整
现在可以回到 CSS 的部份,设定 active 状态下 card 的样式。加入 4 个选择器,分别是 .card.active 移动卡片的位置,.card.active h4 标题的样式,.card.active img 图片的样式,以及 .card.active .content-wrapper 展开内文。
第一步想做的是展开内文,在 .content-wrapper 选择器内,加入 height: 100vh,然后点击卡片试试:
可以看到内文已成功展开,再来是移动卡片,其实除了移动,我们还会放大一点卡片,以填充左右的宽度。
在 .card 选择器内加入 transform,先设定 translateY(),向上移动多少呢,其实需要计算元素与顶部的距离,才能将它紧贴着顶部,我们先暂时设定为 -300px,稍后再计算。
在后面再加入 scale() 将卡片放大一点,放大的比例是 body 宽度除以卡片宽度,即 480 除以 420,transform-origin 中心点设定为 50% 0,点击一下,可以看到已经有八成的效果了。
先做一些样式的微调,展开的状态下,移除圆角的设定,同时也移除图片的圆角设定。然后将图片拉高一些:
再加入一些动画过渡的设定,回到 .card 选择器,加入 transition: .3s all,加速度是 cubic-bezier(0, 1, 0.95, 1.05):
可以打开开发者工具,看到这个加速度的线性图表:
你可以试试拉动一下这条曲线,就可以达到不同的加速度效果,我这个设定就可以做到它打开和收起时有一点回弹的效果。
计算位移
回到 JavaScript 的部份,我们要计算卡片与顶部的距离。定义变量 card_offset_scrolltop,将卡片的 offset().top 减去 window.scrollTop(),即卷动距离。然后将计算结果用 CSS 变量的形式设定到卡片上,变量名称改为 --data-offset-top,设定值乘以 -1 并加上 px 单位。
回到 CSS 的部份,将 .card.active 内 transform 的 translateY() 里面的 -300px 替换成 var(--data-offset-top),再点击测试一下:
可以成功定位到页顶了。
继续优化
你以为已经做完了?不要停,继续优化余下的部份。
现在在展开卡片后,卷动页面的话会穿帮,所以要在打开卡片后,暂停 body 的卷动。
在 JavaScript 的部份,判断如果卡片有 active 这个 class,即是打开的状态下,在 body 上加入 noscroll 这个 class;反之就移除 noscroll 这个 class。
打开 CSS 的部份,加入 body.noscroll 选择器,设定 overflow: hidden,这样就可以达到防止卷动的效果了。
然后再处理内文卷动的部份,在 JavaScript 那边,定义 height 变量,赋值为 window.height()。要计算内文部份的高度,就将 window 高度减去图片高度,再减去标题高度就可以了。
然后将这个高度套用到 .content 容器上,测试一下:
为什么会卷不到底的呢?这样就代表高度的计算有错误,但计算有什么错呢?
给三秒钟时间你想一想,3、2、1。
这是由于我们的卡片,通过 transform 的 scale() 放大了,所以在计算图片与标题高度的时候,要乘以放大比例。
定义一个变量名为 ratio,赋值为 480/420,分别将图片与标题高度乘以 ratio,而最后,又要将总数除以这个 ratio,再测试一下:
今次高度就正确了。
再做最后的一些微调,卷动的时候会发现 APP OF THE DAY 的下方 padding 稍为少了一点,在 .card.active h4 内加入 padding-bottom: .8rem:
然后内文出现的时候加一些渐入效果,在 .content-wrapper 内加入透明度设定 opacity: .8,transition: .3s all ease-out:
再在展开的 .content-wrapper 内加入 opacity: 1,transition: .3s all ease-in,这样在打开与收起的时候就会有不同的加速度效果。
我们来看看这个案例的完成效果
以上,就是今集要介绍的全部内容。
这个案例的源代码在 codepen.io/stevenlei/p…
你的支持是我的动力,请关注 CodingStartup 起码课,我们一起加油!