首先来看一下网站效果,想写这个项目的读者可以自行下载哦,地址:https://github.com/Stray-Kite/Car:
在这个项目中,我们主要是为了学习语种切换,也就是右上角的 中文/English 功能的实现。
首先看一下模拟的后台数据src/config/modules/lang.js 文件中:
关键代码:
1 export default { 2 name: 'Homepage', 3 components: { 4 ScrollNumber 5 }, 6 data () { 7 return { 8 lang: 'chinese', 9 pageIndex: 0, 10 stepIndex: 0 11 } 12 }, 13 ......其余代码 44 methods: { 45 addClass (el, _class) {177 //切换语言 178 toggleLang (lang) { 179 this.lang = lang 180 // this.animatePage() 181 } 182 }, 183 //以下几个computed是获取config文件夹里的数据 184 computed: { 185 langs () { 186 return config.langs[this.lang] //主要靠这里切换,这个切换的本质其实就是使用了另一套英文的数据(换了一套后台数据) 187 }, 188 ......其余代码198 } 199 }
最后给大家一波儿带注解的代码,方便理解:
Homepage.vue(CSS我就不给啦,因为这东西看着学一下就好):
1 <template> 2 <div class="container"> 3 4 5 <div class="rc-header"> 6 <!--首先这是两个切换语言的按钮(中文/English)--> 7 <div class="rc-header-right"> 8 <a href="#" :class="{'is-active': this.lang === 'chinese'}" @click="toggleLang('chinese')">中文</a> / 9 <a href="#" :class="{'is-active': this.lang === 'english'}" @click="toggleLang('english')">English</a> 10 </div> 11 <!--顶栏其他的元素,图片等...--> 12 <div class="rc-header-left"> 13 <img class="rc-logo" :src="media.LOGO_PATH"> 14 <img class="rc-slogan" :src="media.SLOGAN_PATH"> 15 </div> 16 </div> 17 18 <!--下面就是整个内容页了,大的来说这四页就是一个大的竖着的轮播图,用了vue的swiper--> 19 <div class="swiper-container rc-body"> 20 <div class="swiper-wrapper"> 21 22 <!--第一页--> 23 <div class="swiper-slide"> 24 <div class="rc-page rc-page01"> 25 <!--星空和车--> 26 <div class="rc-galaxy"> 27 <div class="rc-bg rc-layer-bg" ref="plbo" 28 :style="{backgroundImage: 'url(' + common.GALAXY_LAYER_BG + ')', top: layerEdge, bottom: layerEdge, left: layerEdge, right: layerEdge}"> 29 </div> 30 <div class="rc-bg rc-layer-top" ref="plto" 31 :style="{backgroundImage: 'url(' + common.GALAXY_LAYER_TOP + ')'}"> 32 </div> 33 </div> 34 35 <div class="rc-content-wrapper"> 36 <!--若是第一页,即index=0,则显示,否则隐藏--> 37 <div class="rc-text-wrapper" ref="ptwo" 38 :style="{visibility: pageIndex === 0 ? 'visible' : 'hidden'}" > 39 <h2>{{langs.PAGE_ONE_SLOGAN}}</h2> 40 <!--scroll-number是数字增加,引入widgets/ScrollNumber.vue的方法--> 41 <scroll-number :size="isMobile ? 18 : 24" ref="psno"> 42 <span slot="suffix" style="color: #c3c3c3;">{{langs.PAGE_ONE_SUB_SLOGAN}}</span> 43 </scroll-number> 44 </div> 45 <button class="btn btn-success btn-try" 46 :style="{visibility: pageIndex === 0 ? 'visible' : 'hidden'}" 47 ref="pbro">{{langs.TRY_LABEL}}</button> 48 </div> 49 50 <!--下面这两个img不用管,已经隐藏啦--> 51 <img :src="common.GALAXY_REAL_TOP" v-show="false"> 52 <img :src="common.GALAXY_REAL_BG" v-show="false"> 53 </div> 54 </div> 55 56 <!--第二页--> 57 <div class="swiper-slide"> 58 <div class="rc-page rc-page02"> 59 <div class="rc-content-wrapper"> 60 <div 61 class="rc-text-wrapper" 62 :style="{visibility: pageIndex === 1 ? 'visible' : 'hidden'}" 63 ref="ptwt"> 64 <h2>{{langs.PAGE_TWO_SLOGAN}}</h2> 65 </div> 66 <div class="rc-step-wrapper"> 67 <div class="rc-step-stage"> 68 <div class="swiper-container rc-step-container"> 69 <div class="swiper-wrapper"> 70 <div 71 v-for="(step, index) in langs.STEPS_PROFILE" 72 :key="index" 73 class="swiper-slide rc-step"> 74 <div class="rc-step-content"> 75 <h2 class="rc-step-title">{{langs.STEP_LABEL}} {{index + 1}}</h2> 76 <p class="rc-step-profile">{{step}}</p> 77 <button 78 class="btn" 79 ref="pbdt" 80 v-if="index === 0">{{langs.DOWNLOAD_LABEL}}</button> 81 <button 82 class="btn" 83 ref="pbat" 84 v-if="index === 1">{{langs.AUTH_LABEL}}</button> 85 </div> 86 </div> 87 </div> 88 <div class="swiper-scrollbar"></div> 89 <div class="swiper-button-next" v-if="!isMobile"></div> 90 <div class="swiper-button-prev" v-if="!isMobile"></div> 91 </div> 92 </div> 93 <!--步骤右面的图片--> 94 <div class="rc-step-scene" v-if="!isMobile"> 95 <div 96 :style="{backgroundImage: 'url(' + media.STEPS_ACTOR_PATH[stepIndex] + ')'}" 97 class="rc-bg rc-step-actor"></div> 98 </div> 99 </div> 100 </div> 101 </div> 102 </div> 103 104 <!--第三页--> 105 <div class="swiper-slide"> 106 <div class="rc-page rc-page03"> 107 <div class="rc-content-wrapper"> 108 <div 109 class="rc-text-wrapper" 110 :style="{visibility: pageIndex === 2 ? 'visible' : 'hidden'}" 111 ref="ptwth"> 112 <h2>{{langs.PAGE_THREE_SLOGAN}}</h2> 113 </div> 114 <div class="rc-cars-gallery"> 115 <div 116 v-for="(car, index) in langs.CARS_PROFILE" 117 :key="index" 118 class="rc-car"> 119 <div class="rc-car-card" :title="langs.CARS_TOOLTIP"> 120 <div class="rc-car-media"> 121 <div 122 :style="{backgroundImage: 'url(' + common.CARS_PATH[index] + ')'}" 123 class="rc-bg rc-bg-scale rc-car-snapshot"></div> 124 </div> 125 <div class="rc-car-profile"> 126 <h4>{{car.NAME}}</h4> 127 <div>{{langs.OWNER_LABEL}}: {{car.OWNER}}</div> 128 <div>{{langs.DATE_LABEL}}: {{car.DATE}}</div> 129 </div> 130 </div> 131 </div> 132 </div> 133 </div> 134 </div> 135 </div> 136 137 <!--第四页--> 138 <div class="swiper-slide"> 139 <div class="rc-page rc-page04"> 140 <div 141 class="rc-text-wrapper" 142 :style="{visibility: pageIndex === 3 ? 'visible' : 'hidden'}" 143 ref="ptwf"> 144 <h2>{{langs.PAGE_FOUR_SLOGAN}}</h2> 145 </div> 146 <div class="rc-content-wrapper"> 147 <div class="rc-bg rc-drive-stage"> 148 <div 149 :style="{backgroundImage: 'url(' + common.DRIVE_STAGE_ACTOR + ')'}" 150 class="rc-bg rc-actor-car" ref="pdcf"></div> 151 </div> 152 </div> 153 </div> 154 </div> 155 </div> 156 <div class="swiper-pagination" v-show="!isMobile"></div> 157 </div> 158 </div> 159 </template> 160 161 <script> 162 /* eslint-disable */ 163 import Swiper from 'swiper' 164 import 'swiper/dist/css/swiper.min.css' 165 import 'animate.css/animate.min.css' 166 import config from '../config' 167 import ScrollNumber from '@/widgets/ScrollNumber.vue' 168 export default { 169 name: 'Homepage', 170 components: { 171 ScrollNumber 172 }, 173 data () { 174 return { 175 lang: 'chinese', 176 pageIndex: 0, 177 stepIndex: 0 178 } 179 }, 180 beforeCreate () { 181 /** 182 * 旋转星空 183 * step 3: 计算可视区域对角线, 根据对角线长度撑开容器 184 */ 185 let w = document.body.clientWidth 186 let h = document.body.clientHeight 187 this.layerEdge = 'calc(50% - ' + Math.ceil(Math.sqrt(w * w + h * h) / 2) + 'px)' 188 }, 189 mounted () { 190 this.$nextTick(function () { 191 let _self = this 192 /* eslint-disable no-new */ 193 new Swiper('.rc-body', { 194 speed: 800, 195 direction: 'vertical', 196 paginationClickable: true, 197 mousewheel: true, 198 pagination: { 199 el: '.swiper-pagination' 200 }, 201 on: { 202 slideChangeTransitionEnd () { 203 _self.pageIndex = this.activeIndex 204 _self.animatePage() 205 } 206 } 207 }) 208 this.initPage() 209 }) 210 }, 211 methods: { 212 addClass (el, _class) { 213 let elClassArr = el.className.split(' ') //用单个空格分割 214 let classArr = _class.split(' ') //同上 215 classArr.forEach(item => { 216 // 如果elClassArr中没有这个元素(类型),那么添加进去 217 if (elClassArr.indexOf(item) === -1) { 218 elClassArr.push(String(item)) 219 } 220 }) 221 //添加完后本属性后面要有一个空格隔开 222 el.className = elClassArr.join(' ') 223 return el 224 }, 225 removeClass (el, _class) { 226 let elClassArr = el.className.split(' ') 227 let classArr = _class.split(' ') 228 classArr.forEach(item => { 229 let index = elClassArr.indexOf(item) 230 if (index > -1) { 231 elClassArr.splice(index, 1) 232 } 233 }) 234 el.className = elClassArr.join(' ') 235 return el 236 }, 237 bindAnimation (el, x) { 238 let _self = this 239 // 监听动画结束事件 240 let events = [ 241 'webkitAnimationEnd', 242 'mozAnimationEnd', 243 'MSAnimationEnd', 244 'oanimationend', 245 'animationend' 246 ] 247 _self.addClass(el, x + ' animated') 248 events.forEach(event => { 249 let func = function () { 250 events.forEach(item => { 251 el.removeEventListener(item, func) 252 }) 253 _self.removeClass(el, x + ' animated') 254 } 255 //增加事件监听,如果时间结束,那么再利用fun函数移除监听的事件和添加进来的新类(class属性) 256 el.addEventListener(event, func) 257 }) 258 }, 259 initPage01 () { 260 setTimeout(() => { 261 /** 262 * 旋转星空 263 * step 4: 采用真实图片替换代理图片 264 */ 265 this.$refs.plto.style.backgroundImage = 'url(' + this.common.GALAXY_REAL_TOP + ')' 266 this.$refs.plbo.style.backgroundImage = 'url(' + this.common.GALAXY_REAL_BG + ')' 267 }, 2000) 268 this.animatePage01() //动画效果(数字上升) 269 }, 270 //第二页 271 initPage02 () { 272 let _self = this 273 let pbdt = this.$refs.pbdt[0] 274 let pbat = this.$refs.pbat[0] 275 let bindAnimation = this.bindAnimation 276 new Swiper('.rc-step-container', { 277 speed: 600, 278 scrollbar: { 279 el: '.swiper-scrollbar', 280 draggable: true 281 }, 282 navigation: { 283 nextEl: '.swiper-button-next', 284 prevEl: '.swiper-button-prev' 285 }, 286 on: { 287 slideChangeTransitionEnd () { 288 if (this.activeIndex === 0) { 289 bindAnimation(pbdt, 'tada') 290 } else if (this.activeIndex === 1) { 291 bindAnimation(pbat, 'tada') 292 } 293 _self.stepIndex = this.activeIndex 294 } 295 } 296 }) 297 }, 298 initPage03 () {}, 299 initPage04 () {}, 300 301 //初始化四个界面方法 302 initPage () { 303 this.initPage01() 304 this.initPage02() 305 this.initPage03() 306 this.initPage04() 307 }, 308 309 animatePage01 () { 310 this.bindAnimation(this.$refs.ptwo, 'fadeIn') 311 this.bindAnimation(this.$refs.pbro, 'fadeIn') 312 this.$refs.psno.$emit('start') 313 }, 314 animatePage02 () { 315 this.bindAnimation(this.$refs.ptwt, 'fadeInDown') 316 }, 317 animatePage03 () { 318 this.bindAnimation(this.$refs.ptwth, 'flipInY') 319 }, 320 animatePage04 () { 321 this.bindAnimation(this.$refs.ptwf, 'jackInTheBox') 322 setTimeout(() => { 323 this.bindAnimation(this.$refs.pdcf, 'rc-arrive') 324 }, 600) 325 }, 326 // QA: 函数本是对象 327 animatePage (index) { 328 [ 329 this.animatePage01, 330 this.animatePage02, 331 this.animatePage03, 332 this.animatePage04 333 ][index || this.pageIndex]() 334 }, 335 //切换语言 336 toggleLang (lang) { 337 this.lang = lang 338 // this.animatePage() 339 } 340 }, 341 //以下几个computed是获取config文件夹里的数据 342 computed: { 343 langs () { 344 return config.langs[this.lang] 345 }, 346 media () { 347 return config.media[this.lang] 348 }, 349 common () { 350 return config.common 351 }, 352 // 判断移动端,无需记住,会用就好,返回值为bool值 353 isMobile () { 354 return 这个就不给了,这个直接用就好了355 } 356 } 357 } 358 </script> 359 360 <style scoped> 361 </style>
widgets/ScrollNumber.vue(数字增长):
1 <template> 2 <div class="scroll-number" :style="{fontSize: size + 'px'}"> 3 <slot name="prefix"></slot> 4 <span class="number">{{value}}</span> 5 <slot name="suffix"></slot> 6 </div> 7 </template> 8 9 <script> 10 import TWEEN from 'tween.js' 11 export default { 12 name: 'ScrollNumber', 13 data () { 14 return { 15 value: 0, 16 animation: null 17 } 18 }, 19 props: { 20 total: { 21 type: Number, 22 default: 15374 23 }, 24 size: { 25 type: Number, 26 default: 24 27 } 28 }, 29 mounted () { 30 this.$nextTick(function () { 31 const animate = function (time) { 32 requestAnimationFrame(animate) 33 TWEEN.update(time) 34 } 35 requestAnimationFrame(animate) 36 let _self = this 37 // 增长起始值,从0开始 38 let surface = {value: 0} 39 // 增加从0增长到total动画1600为时间 40 this.animation = new TWEEN.Tween(surface).to({value: _self.total}, 1600) 41 this.animation.easing(TWEEN.Easing.Exponential.Out).onUpdate(() => { 42 _self.value = Math.floor(surface.value) 43 }) 44 // 监听父组件通讯事件 45 this.$on('start', () => { 46 surface.value = 0 47 this.animation.start() 48 }) 49 }) 50 } 51 } 52 </script> 53 54 <style scoped> 55 .number { 56 display: inline-block; 57 min-width: 40px; 58 color: orangered; 59 text-align: center; 60 font-weight: 600; 61 } 62 </style>
注:其中TWEEN的用法我找到了一篇比较不错的文章,地址:https://www.jianshu.com/p/164538a89939