2017-05-05 | 17 min read 最近重新写了 Ghost-theme 的主题。其中主要是取消了Icon-font,改为了SVG Sprite 。其次非常重要的板块首屏的Slide 和导航重新设计了。先看下具体的效果吧: 视频无法播放,可以查看 gif 图片 Slide 切换 切换动画 slide 的切换主要是利用了transform 和 animation 两个重要的属性。而切换的时间函数则需要用到 cubic-bezier(关于贝塞尔曲线的效果可以参考这里)。然后利用少量的JS和手势库就可以完成了。 基本结构 <div id="slideshow" class="slideshow"> <!-- slide --> <div class="slide"> <div class="bg-overlay" style="background-image:url(/content/images/2017/04/rocket-in-dark-1.jpg)"></div> <blockquote> <p>破立</p> <a class="view-details noSwipe" href="/fan-fu/"> 阅读详情 <svg style="margin-top:2px;" class="icon icon-fast-forward"><use xlink:href="#icon-fast-forward"></use></svg> </a> </blockquote> </div> <!-- end slide --> <!-- repeat --> </div> 其中我们可以看到容器 slideshow 里面 包含你需要存放的 slide 单元。每个 slide 由一张背景图片,和标题以及导航链接组成。下来我们添加一些样式,整个slide都是用的绝对定位,占满首屏: .slideshow, .slide, .slide::after { width: 100%; height: 100% } .slideshow { margin: 0; padding: 0; overflow: hidden; position: relative; transition: margin .6s; -webkit-transition: margin .6s; transition-timing-function: cubic-bezier(0.7,0,0.3,1); -webkit-transition-timing-function: cubic-bezier(0.7,0,0.3,1); background-color: #2c3e50 } 关于这使用的transition后文的导航会涉及到。 接下来,我们添加slide里面的样式, .slide { position: absolute; top: 0; left: 0; opacity: 0; pointer-events: none; background: #fff; -webkit-transform-origin: 50% 120%; transform-origin: 50% 120% } .slide::after { position: absolute; left: 0; top: 0; z-index: 1000; background: #fff; content: ''; opacity: 0; -webkit-transition: opacity 4s; transition: opacity 4s; -webkit-transition-timing-function: cubic-bezier(0.7,0,0.3,1); transition-timing-function: cubic-bezier(0.7,0,0.3,1) } .slideshow blockquote p { overflow: hidden; text-overflow: ellipsis; padding: 0 .25rem } .slideshow blockquote { z-index: 1001; position: absolute; bottom: 3rem; left: 3rem; right: 3rem; padding: 0; padding-bottom: 1rem; border-left: none; color: #fff; text-align: left } .slideshow .bg-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-position: center center; background-repeat: no-repeat; background-size: cover; box-shadow: 0 -2rem 3rem -2rem rgba(0,0,0,0.45) inset,0 -3rem 3rem -2rem rgba(0,0,0,0.45) inset,0 -4rem 5rem -4rem rgba(0,0,0,0.35) inset,0 -10rem 3rem -3rem rgba(0,0,0,0.35) inset,0 -15rem 3rem -3rem rgba(0,0,0,0.35) inset,0 -20rem 5rem -3rem rgba(0,0,0,0.35) inset; transition: opacity 0.6s; -webkit-transition: opacity 0.6s; -webkit-transition-timing-function: cubic-bezier(0.7,0,0.3,1); transition-timing-function: cubic-bezier(0.7,0,0.3,1) } .open .slideshow .bg-overlay { opacity: 0 } .slideshow blockquote p { margin: 0; margin-bottom: 2.5rem; padding: 0; font-size: 4.8rem; font-style: normal; line-height: 1.2em } .slideshow blockquote .date { font-size: 1.1rem; color: #ccc } .slideshow blockquote .view-details { display: inline-block; padding: .5rem 1rem; border: 1px solid rgba(255,255,255,0.55); border-radius: .2rem; color: #fff; font-size: 1.4rem; line-height: 1.2em; box-shadow: 0 2px 4px -2px rgba(0,0,0,0.25) } 其中 .slide 是使用的绝对定位,其中关键的是设置它的旋转中心,从上面的动图也可以看到是在中心旋转。而blockquote则设置的用于显示一些基本的标题和链接挑战的样式设置。 接下来我们来实现具体的动画效果: 我们动画分为四种,即逆时针淡入与淡出,顺时针淡入与淡出。 首先我们先添加整体的动画函时间函数控制。这里主要是使用的 cubic-bezier(0.7,0,0.3,1)。 .slider--animInRight, .slider--animInLeft, .slider--animOutRight, .slider--animOutLeft { -webkit-animation-timing-function: cubic-bezier(0.7,0,0.3,1); animation-timing-function: cubic-bezier(0.7,0,0.3,1); -webkit-animation-duration: .9s; animation-duration: .9s; -webkit-animation-fill-mode: forwards; animation-fill-mode: forwards } 我们添加图片逆时针旋转进入的动画效果,slide上面的一张会向左旋转一定角度,然后显示在最上层。 .slider--animInLeft { -webkit-animation-name: animInLeft; animation-name: animInLeft } .slider--animInLeft { z-index: 101; opacity: 1 } @-webkit-keyframes animInLeft { from { -webkit-transform: rotate3d(0,0,1,-20deg) translate3d(-100%,0,0); opacity: 0 } to { -webkit-transform: rotate3d(0,0,1,0deg) translate3d(0,0,0); transform: rotate3d(0,0,1,0deg) translate3d(0,0,0) } } @keyframes animInLeft { from { -webkit-transform: rotate3d(0,0,1,-20deg) translate3d(-100%,0,0); transform: rotate3d(0,0,1,-20deg) translate3d(-100%,0,0); opacity: 0 } to { -webkit-transform: rotate3d(0,0,1,0deg) translate3d(0,0,0); transform: rotate3d(0,0,1,0deg) translate3d(0,0,0) } } 同理,我们添加从右进入的动画效果: .slider--animInRight { z-index: 101; opacity: 1 } .slider--animInRight { -webkit-animation-name: animInRight; animation-name: animInRight } @-webkit-keyframes animInRight { from { -webkit-transform: rotate3d(0,0,1,20deg) translate3d(100%,0,0); transform: rotate3d(0,0,1,20deg) translate3d(100%,0,0); opacity: 0 } to { -webkit-transform: rotate3d(0,0,1,0deg) translate3d(0,0,0); transform: rotate3d(0,0,1,0deg) translate3d(0,0,0) } } @keyframes animInRight { from { -webkit-transform: rotate3d(0,0,1,20deg) translate3d(100%,0,0); transform: rotate3d(0,0,1,20deg) translate3d(100%,0,0); opacity: 0 } to { -webkit-transform: rotate3d(0,0,1,0deg) translate3d(0,0,0); transform: rotate3d(0,0,1,0deg) translate3d(0,0,0) } } 其中我们还需要给进入的样式中设置:after伪类的效果,让它的的透明度变为1。 .slider--animOutRight::after, .slider--animOutLeft::after { opacity: 1 } 同理,我们可以添加淡出的动画,分为左右两个方向。 .slider--animOutRight { -webkit-animation-name: animOutRight; animation-name: animOutRight } .slider--animOutLeft { -webkit-animation-name: animOutLeft; animation-name: animOutLeft } @-webkit-keyframes animOutLeft { to { -webkit-transform: rotate3d(0,0,1,-20deg) translate3d(-100%,0,0); transform: rotate3d(0,0,1,-20deg) translate3d(-100%,0,0); opacity: 0 } } @keyframes animOutLeft { to { -webkit-transform: rotate3d(0,0,1,-20deg) translate3d(-100%,0,0); transform: rotate3d(0,0,1,-20deg) translate3d(-100%,0,0); opacity: 0 } } @-webkit-keyframes animOutRight { to { -webkit-transform: rotate3d(0,0,1,20deg) translate3d(100%,0,0); transform: rotate3d(0,0,1,20deg) translate3d(100%,0,0); opacity: 0 } } @keyframes animOutRight { to { -webkit-transform: rotate3d(0,0,1,20deg) translate3d(100%,0,0); transform: rotate3d(0,0,1,20deg) translate3d(100%,0,0); opacity: 0 } } 这样我们的样式基本就到位了,我们需要设置当前显示slider的样式。 .slider--current { position: absolute; z-index: 100; opacity: 1; pointer-events: auto } 这样我们的基本效果就呈现了。 接着我们添加一些边角料的样式,比如用于控制的左右切换的按钮以及slide的缩略图。 .slideshow .ctrl { z-index: 101; position: absolute; bottom: 3rem; right: 3rem } .slideshow .ctrl a { color: rgba(255,255,255,0.75); margin: .5rem } .slideshow .ctrl a:hover,.slideshow .ctrl a.active { color: #fff } .slideshow .thumb { z-index: 101; position: absolute; bottom: 3rem; left: 3.25rem } .slideshow .thumb a { display: inline-block; width: .5rem; height: .5rem; margin: .25rem; border-radius: .5rem; background-color: rgba(255,255,255,0.45) } .slideshow .thumb a.active { background-color: rgba(255,255,255,0.85) } 这些都是动态通过JS添加的,接下来我们添加JS来控制逻辑。首先我们需要引入 jQuery的库。 /* globals jQuery, document */ (function ($) { "use strict"; var $document = $(document), containers = [].slice.call($('.slide')), containersCount = containers.length, current = 0, isAnimating = false, pageTriggers = []; var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); $document.ready(function () { initSlider(); }); function initSlider() { var thumbString = ''; containers.forEach(function (item, index) { thumbString += '<a href="javascript:;" ' + (index === 0 ? 'class="active" ' : '') + '></a>'; }); if (containers.length > 1) { var ctrlString = ''; if (!isMobile) { ctrlString = '<div class="ctrl"><a href="javascript:;" class="js-ctrl-left" ><svg class="icon icon-rewind"><use xlink:href="#icon-rewind"></use></svg></a><a href="javascript:;" class="js-ctrl-right icon-angle-right" ><svg class="icon icon-fast-forward"><use xlink:href="#icon-fast-forward"></use></svg></a></div>'; } $('#slideshow').append([ctrlString, '<div class="thumb">', thumbString, '</div>'].join('')); bindEvent(); } pageTriggers = [].slice.call($('.thumb a')); $(containers[current]).addClass('slider--current'); } function bindEvent() { $('.js-ctrl-left').on('click', function () { if (current > 0) { navigate(pageTriggers[current - 1]); } }); $('.js-ctrl-right').on('click', function () { if (current < containersCount - 1) { navigate(pageTriggers[current + 1]); } }); function navigate(pageTrigger) { var oldcurrent = current, newcurrent = pageTriggers.indexOf(pageTrigger); if (isAnimating || oldcurrent === newcurrent) return; isAnimating = true; var currentPageTrigger = pageTriggers[current], nextContainer = containers[newcurrent], currentContainer = containers[current], dir = newcurrent > oldcurrent ? 'left' : 'right'; $(currentPageTrigger).removeClass('active'); $(pageTrigger).addClass('active'); // update current current = newcurrent; $(nextContainer).addClass(dir === 'left' ? 'slider--animInRight' : 'slider--animInLeft'); $(currentContainer).addClass(dir === 'left' ? 'slider--animOutLeft' : 'slider--animOutRight'); onEndAnimation(currentContainer, function () { $(currentContainer).removeClass(dir === 'left' ? 'slider--animOutLeft' : 'slider--animOutRight'); $(nextContainer).removeClass(dir === 'left' ? 'slider--animInRight' : 'slider--animInLeft'); $(currentContainer).removeClass('slider--current'); $(nextContainer).addClass('slider--current'); isAnimating = false; }); } })(jQuery); 这样我们就可以就可以看到基本的效果了,其中 navigate 为最为核心的函数。主要是依据当前的slide然后去进行方向的选择,也就是添加不同的 class 。 我们看到其中有用到一些svg图标,所以我们还必须先在body里添加svg图标: <!--svg --> <svg style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg"> <defs> <symbol id="icon-ribbon" viewBox="0 0 24 24"> <title>ribbon</title> <path d="M7 1h10q1.242 0 2.121 0.879t0.879 2.121v19l-8-6-8 6v-19q0-1.242 0.879-2.121t2.121-0.879zM17 3h-10q-0.414 0-0.707 0.293t-0.293 0.707v15l6-4.5 6 4.5v-15q0-0.414-0.293-0.707t-0.707-0.293z"></path> </symbol> <symbol id="icon-image" viewBox="0 0 24 24"> <title>image</title> <path d="M4 1h16q1.242 0 2.121 0.879t0.879 2.121v16q0 1.242-0.879 2.121t-2.121 0.879h-16q-1.242 0-2.121-0.879t-0.879-2.121v-16q0-1.242 0.879-2.121t2.121-0.879zM21 17.414l-5-5-8.586 8.586h12.586q0.414 0 0.707-0.293t0.293-0.707v-2.586zM20 3h-16q-0.414 0-0.707 0.293t-0.293 0.707v16q0 0.414 0.293 0.707t0.707 0.293h0.586l11.414-11.414 5 5v-10.586q0-0.414-0.293-0.707t-0.707-0.293zM8 5q1.242 0 2.121 0.879t0.879 2.121-0.879 2.121-2.121 0.879-2.121-0.879-0.879-2.121 0.879-2.121 2.121-0.879zM8 7q-0.414 0-0.707 0.293t-0.293 0.707 0.293 0.707 0.707 0.293 0.707-0.293 0.293-0.707-0.293-0.707-0.707-0.293z"></path> </symbol> <symbol id="icon-search" viewBox="0 0 24 24"> <title>search</title> <path d="M10 1q1.828 0 3.496 0.715t2.871 1.918 1.918 2.871 0.715 3.496q0 1.57-0.512 3.008t-1.457 2.609l5.68 5.672q0.289 0.289 0.289 0.711 0 0.43-0.285 0.715t-0.715 0.285q-0.422 0-0.711-0.289l-5.672-5.68q-1.172 0.945-2.609 1.457t-3.008 0.512q-1.828 0-3.496-0.715t-2.871-1.918-1.918-2.871-0.715-3.496 0.715-3.496 1.918-2.871 2.871-1.918 3.496-0.715zM10 3q-1.422 0-2.719 0.555t-2.234 1.492-1.492 2.234-0.555 2.719 0.555 2.719 1.492 2.234 2.234 1.492 2.719 0.555 2.719-0.555 2.234-1.492 1.492-2.234 0.555-2.719-0.555-2.719-1.492-2.234-2.234-1.492-2.719-0.555z"></path> </symbol> <symbol id="icon-rewind" viewBox="0 0 24 24"> <title>rewind</title> <path d="M18 4q0.414 0 0.707 0.293t0.293 0.707-0.297 0.711l-6.289 6.289 6.289 6.289q0.297 0.297 0.297 0.711t-0.293 0.707-0.707 0.293q-0.422 0-0.711-0.289l-7-7q-0.289-0.289-0.289-0.711t0.289-0.711l7-7q0.289-0.289 0.711-0.289zM12 4q0.414 0 0.707 0.293t0.293 0.707-0.297 0.711l-6.289 6.289 6.289 6.289q0.297 0.297 0.297 0.711t-0.293 0.707-0.707 0.293q-0.422 0-0.711-0.289l-7-7q-0.289-0.289-0.289-0.711t0.289-0.711l7-7q0.289-0.289 0.711-0.289z"></path> </symbol> <symbol id="icon-fast-forward" viewBox="0 0 24 24"> <title>fast-forward</title> <path d="M11 4q0.414 0 0.703 0.289l7 7q0.297 0.297 0.297 0.711t-0.297 0.711l-7 7q-0.289 0.289-0.703 0.289t-0.707-0.293-0.293-0.707q0-0.422 0.289-0.711l6.289-6.289-6.289-6.289q-0.289-0.289-0.289-0.711 0-0.43 0.285-0.715t0.715-0.285zM5 4q0.414 0 0.703 0.289l7 7q0.297 0.297 0.297 0.711t-0.297 0.711l-7 7q-0.289 0.289-0.703 0.289t-0.707-0.293-0.293-0.707q0-0.422 0.289-0.711l6.289-6.289-6.289-6.289q-0.289-0.289-0.289-0.711 0-0.43 0.285-0.715t0.715-0.285z"></path> </symbol> <symbol id="icon-speech-bubble" viewBox="0 0 24 24"> <title>speech-bubble</title> <path d="M4 1h16q1.242 0 2.121 0.879t0.879 2.121v11q0 1.242-0.879 2.121t-2.121 0.879h-8l-7 5v-5h-1q-1.242 0-2.121-0.879t-0.879-2.121v-11q0-1.242 0.879-2.121t2.121-0.879zM20 3h-16q-0.414 0-0.707 0.293t-0.293 0.707v11q0 0.414 0.293 0.707t0.707 0.293h3v3.117l4.359-3.117h8.641q0.414 0 0.707-0.293t0.293-0.707v-11q0-0.414-0.293-0.707t-0.707-0.293z"></path> </symbol> <symbol id="icon-pie-graph" viewBox="0 0 24 24"> <title>pie-graph</title> <path d="M12 1q2.242 0 4.277 0.871t3.508 2.344 2.344 3.508 0.871 4.277-0.871 4.277-2.344 3.508-3.508 2.344-4.277 0.871-4.277-0.871-3.508-2.344-2.344-3.508-0.871-4.277 0.871-4.277 2.344-3.508 3.508-2.344 4.277-0.871zM11 13v-9.945q-1.664 0.188-3.145 0.965t-2.547 1.957-1.688 2.75-0.621 3.273q0 1.828 0.715 3.496t1.918 2.871 2.871 1.918 3.496 0.715q1.703 0 3.273-0.621t2.75-1.688 1.957-2.547 0.965-3.145h-9.945zM13 3.055v7.945h7.945q-0.172-1.547-0.852-2.934t-1.727-2.434-2.434-1.727-2.934-0.852z"></path> </symbol> <symbol id="icon-star" viewBox="0 0 24 24"> <title>star</title> <path d="M9.297 9l2.703-8.32 2.703 8.32h8.75l-7.078 4.914 2.703 8.172-7.078-5.18-7.078 5.141 2.703-8.133-7.078-4.914h8.75zM13.25 11l-1.25-3.977-1.25 3.977h-3.883l3.125 2.070-1.227 3.695 3.242-2.344 3.227 2.367-1.227-3.695 3.117-2.094h-3.875z"></path> </symbol> </defs> </svg> <!--svg end--> 添加图标样式 .icon { display: inline-block; width: 1em; height: 1em; stroke-width: 0; stroke: currentColor; fill: currentColor; vertical-align: middle } 导航的动画 从动画中,我们还看到了向下滑的时候,会看到导航的链接出现,也就是点击右上角的 图标按钮 后出现的效果。接下来我们再添加这样的效果。 <div class="nav"> <div class="container"> <div class="nav-list"> <div class="avatar"> <img src="http://img1.vued.vanthink.cn/vuedf926d9e80d3eb33c504228105f872e3c.png" /> </div> <div class="quote"> Jack Pu's Blog (蒲小花的博客-ポーのブログ) </div> <a href="http://www.jackpu.com/">Home</a> <a href="https://www.behance.net/codeui">Design</a> <a href="http://www.jackpu.com/tag/web/">Web</a> <a href="http://me.jackpu.com/">About Me</a> </div> <a href="javascript:;" class="menu-icon open"> <svg width="100%" height="100%" viewBox="0 0 60 60" preserveAspectRatio="none"> <g id="icon-grid"> <rect x="32.5" y="5.5" width="22" height="22"></rect> <rect x="4.5" y="5.5" width="22" height="22"></rect> <rect x="32.5" y="33.5" width="22" height="22"></rect> <rect x="4.5" y="33.5" width="22" height="22"></rect> </g> <g id="icon-cross"> <line x1="4.5" y1="55.5" x2="54.953" y2="5.046"></line> <line x1="54.953" y1="55.5" x2="4.5" y2="5.047"></line> </g> </svg> </a> </div> </div> 基本的结构,就是简单的导航链接和控制的svg按钮。 接下来我们添加它的基本样式,它默认是只会出现一个 如下图的按钮菜单。 .nav { overflow: hidden; z-index: 1000; position: absolute; top: 0; left: 0; right: 0; height: 7rem; margin-bottom: 0; text-align: center; font-size: 1.5rem; opacity: 1; transition: height .6s; transition-timing-function: cubic-bezier(0.7,0,0.3,1) } .nav .container { position: relative; width: 1170px; height: 100%; line-height: 7rem; margin: 0 auto } .open .nav { height: 100%; background-color: #fff } .open .nav .nav-list { opacity: 1; transform: translateY(0) } .nav-list { opacity: 0; padding-top: 5rem; margin: 0 4rem; text-align: center; font-size: 2rem; line-height: 4rem; transition: all .65s; transform: translateY(-520px); transition-timing-function: cubic-bezier(0.7,0,0.3,1) } .nav-list .avatar { display: inline-block; width: 12rem; height: 12rem; border-radius: 6rem; border: 0.3rem solid #111; text-align: center; overflow: hidden } .nav-list .avatar img { width: 100%; height: 100% } .nav-list .quote { font-size: 1.8rem; color: #777 } .nav-list a { overflow: hidden; display: block; width: 20rem; height: 4rem; margin: 0 auto; border-radius: 2rem; white-space: nowrap; text-overflow: ellipsis; transition: all .25s ease; text-decoration: underline; color: #333; transition: all .2s ease } .nav-list a:active { background: linear-gradient(to left,#ee8326,#ef1d27); color: #fff; text-decoration: none } .nav-list a:hover { color: #000 } .menu-icon { position: absolute; right: 0; bottom: 0; width: 2.4rem } .menu-icon #icon-grid,.menu-icon #icon-cross { opacity: 0; stroke: #f1f1f1; stroke-width: 2px; fill: none; transition: all 0.3s ease-in } .menu-icon #icon-cross { opacity: 1 } .menu-icon.open #icon-cross { opacity: 0 } .menu-icon.open #icon-grid { opacity: 1 } .open .nav .menu-icon #icon-cross { stroke: #333 } 其中,我们可以看到会有 .open 这个 class的存在,实际上它是作用于 body 上的。而添加 open 后,按钮的样式会有小方块变成关闭的按钮,这是用过 svg 的样式控制来实现的。而 nav 这个导航的样式也会有所变化 高度会变成 100%。同时,下面的 slideshow 也会配合动画效果,有下面的样式变化: .open .slideshow { margin-top: 480px } .open .slideshow .bg-overlay { opacity: 0 } 其中距离顶部高度会变化,而且里面的图片也会变得透明,这样会有淡出的效果。 当然我们需要在 JS 中添加这些控制: $('.menu-icon').on('click', function (e) { $('body').toggleClass('open'); $(this).toggleClass('open'); }); See the Pen Slide Show by Jack Pu (@Jack_Pu) on CodePen. 添加手势 对于手势的支持,我们需要引入 jquery.swipe,这个时候我们添加对于手势的处理, $("#slideshow").swipe({ //Generic swipe handler for all directions swipe: function (event, direction, distance, duration, fingerCount, fingerData) { if (direction == 'left') { if (current < containersCount - 1) { navigate(pageTriggers[current + 1]); } } else if (direction == 'right') { if (current > 0) { navigate(pageTriggers[current - 1]); } } else if (direction == 'down') { var sT = $('html').scrollTop(); if(sT == 0) { $('body').toggleClass('open'); $('.menu-icon').toggleClass('open'); } else { $('html').animate({'scrollTop': 0},500); } } else if(direction == 'up'){ return false; } }, threshold: 0, allowPageScroll: 'vertical', }); } 完整代码 这样我们就实现了这次新的 slide 的效果, 你可以点击回到主页查看效果。主题项目 github 地址: https://github.com/JackPu/ghost-theme 参考 Sliding Header Layout TouchSwipe Please enable JavaScript to view the comments powered by Disqus.