<项目一>
1、首页采用轮播图动态效果
1、先封装一个动画函数
思路:
形参有添加动画的对象 动画停下的目的地 回调函数;
首先清除所有的定时器;
然后给该对象设置定时器,先声明步长,当对象距离左侧页边距等于目的地的时候清除定时器,反之给他增加步长一直移动
function animate(obj, target, callback) {
// console.log(callback); callback = function() {} 调用的时候 callback()
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function() {
// 步长值写到定时器的里面
// 把我们步长值改为整数 不要出现小数的问题
// var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
// if (callback) {
// // 调用函数
// callback();
// }
callback && callback();
}
// 把每次加1 这个步长值改为一个慢慢变小的值 步长公式:(目标值 - 现在的位置) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}, 15);
}
2、当页面加载的时候就给一个轮播函数
思路:
获取网页元素
当鼠标经过的时候隐藏左右按钮并且清除动画效果
当鼠标离开的时候显示
动态生成小圆圈,有几张图片就生成几个小圆圈
点击小圆圈切换到对应图片
点击下一张按钮,就切换到下一张图片
点击上一张按钮,就切换到上一张图片
开启自动轮播
window.addEventListener('load', function() {
// 1. 获取元素
var arrow_l = document.querySelector('.arrow-l');
var arrow_r = document.querySelector('.arrow-r');
var focus = document.querySelector('.focus');
var focusWidth = focus.offsetWidth;
// 2. 鼠标经过focus 就显示隐藏左右按钮 并且清除图片自动播放效果
focus.addEventListener('mouseenter', function() {
arrow_l.style.display = 'block';
arrow_r.style.display = 'block';
clearInterval(timer);
timer = null; // 清除定时器变量 释放空间
});
focus.addEventListener('mouseleave', function() {
arrow_l.style.display = 'none';
arrow_r.style.display = 'none';
timer = setInterval(function() {
//鼠标离开时 开启自动播放轮播图功能
//手动调用点击事件
arrow_r.click();
}, 2000);
});
// 3. 动态生成小圆圈 有几张图片,我就生成几个小圆圈
var ul = focus.querySelector('ul');
var ol = focus.querySelector('.circle');
// console.log(ul.children.length);
for (var i = 0; i < ul.children.length; i++) {
// 创建一个小li
var li = document.createElement('li');
// 记录当前小圆圈的索引号 通过自定义属性来做
li.setAttribute('index', i);
// 把小li插入到ol 里面
ol.appendChild(li);
// 4. 小圆圈的排他思想 我们可以直接在生成小圆圈的同时直接绑定点击事件
li.addEventListener('click', function() {
// 干掉所有人 把所有的小li 清除 current 类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下我自己 当前的小li 设置current 类名
this.className = 'current';
// 5. 点击小圆圈,移动图片 当然移动的是 ul 因为li是浮动的 所以不能移动li
// ul 的移动距离 小圆圈的索引号 乘以 图片的宽度 注意是负值
// 当我们点击了某个小li 就拿到当前小li 的索引号
var index = this.getAttribute('index');
// 当我们点击了某个小li 就要把这个li 的索引号给 num 控制li
num = index;
// 当我们点击了某个小li 就要把这个li 的索引号给 circle 控制小圆圈
circle = index;
// num = circle = index;
//var focusWidth = focus.offsetWidth; 上边已经获取了
console.log(focusWidth);
console.log(index);
animate(ul, -index * focusWidth);
})
}
// 把ol里面的第一个小li设置类名为 current
ol.children[0].className = 'current';
// 6. 克隆第一张图片(li)放到ul 最后面 为了解决小圆圈会多一个的问题
/* var first = ul.children[0].cloneNode(true);
ul.appendChild(first); */
// 7. 点击右侧按钮, 图片滚动一张
var num = 0;
// circle 控制小圆圈的播放
var circle = 0;
// flag 节流阀 防止 按钮点击过快图片切换不过来的问题
var flag = true; //先打开节流阀
arrow_r.addEventListener('click', function() {
if (flag) {
flag = false; // 然后在程序已执行的时候就紧接着关闭节流阀
// 如果走到了最后复制的一张图片ul.children.length - 1,此时 我们的ul 要快速复原 left 改为 0
if (num == ul.children.length - 1) {
ul.style.left = 0;
num = 0;
}
num++;
animate(ul, -num * focusWidth, function() {
flag = true; // 在动画调用完毕之后才在打开节流阀
});
// 8. 点击右侧按钮,小圆圈跟随一起变化 可以再声明一个变量控制小圆圈的播放
circle++;
// 如果circle == 4 说明走到最后我们克隆的这张图片了 我们就复原
if (circle == ol.children.length) {
circle = 0;
}
// 调用函数
circleChange();
}
});
// 9. 左侧按钮做法
arrow_l.addEventListener('click', function() {
if (flag) {
flag = false;
if (num == 0) {
num = ul.children.length - 1;
ul.style.left = -num * focusWidth + 'px';
}
num--;
animate(ul, -num * focusWidth, function() {
flag = true;
});
// 点击左侧按钮,小圆圈跟随一起变化 可以再声明一个变量控制小圆圈的播放
circle--;
// 如果circle < 0 说明第一张图片,则小圆圈要改为第4个小圆圈(3)
// if (circle < 0) {
// circle = ol.children.length - 1;
// }
circle = circle < 0 ? ol.children.length - 1 : circle;
// 调用函数
circleChange();
}
});
//因为左侧和右侧的功能是一样的 所以封装成了一个函数
function circleChange() {
// 先清除其余小圆圈的current类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下当前的小圆圈的current类名
ol.children[circle].className = 'current';
}
// 10. 自动播放轮播图
var timer = setInterval(function() {
//手动调用点击事件
arrow_r.click();
}, 2000);
})
2、列表页实现点击回到顶部功能
思路:
1、首先获取网页元素
2、先给一个回到顶部的盒子,然后隐藏
3、当页面滚动一定距离的时候显示该盒子
if (window.pageYOffset >= nav.offsetTop) {
goBack.style.display = 'block';
} else {
goBack.style.display = 'none';
}
4、点击该盒子,回到页面顶部 window.scroll(0, 0)
var goBack = document.querySelector('.goBack');
var nav = document.querySelector('nav');
window.addEventListener('scroll', function() {
if (window.pageYOffset >= nav.offsetTop) {
goBack.style.display = 'block';
} else {
goBack.style.display = 'none';
}
});
goBack.addEventListener('click', function() {
window.scroll(0, 0);
})
3、详情页放大效果的实现
思路:
1、先获取元素
2、当鼠标经过盒子的时候显示遮罩层和大盒子,当鼠标离开的时候隐藏遮罩层和大盒子
3、当鼠标经过的时候,让遮罩层跟着鼠标的中心走(鼠标在盒子中位置的确定就是鼠标距离左侧页边距减去盒子距离左侧页边距),让鼠标始终在遮罩层中心(遮罩层的位置就是鼠标在盒子中的位置减去遮罩层宽度的一半)
4、设置遮罩层的边界 (遮罩层移动的最大距离就是盒子宽度减去遮罩层宽度,当遮罩层位置小于等于 0 的时候就停在 0 ;当遮罩层的位置 大于等于 0 的时候就停在最大位置;)
5、大图片跟着遮罩层一起移动。大图片移动的最大距离(外边大盒子的宽度减去大图片的宽度),
遮罩层的移动距离 / 遮罩层的最大移动距离 = 大图片的移动距离 / 大图片移动的最大距离
所以大图片的移动 = 遮罩层的移动距离 / 遮罩层的最大移动距离 * 大图片移动的最大距离
window.addEventListener('load', function() {
var preview_img = document.querySelector('.preview_img');
var mask = document.querySelector('.mask');
var big = document.querySelector('.big');
// 1. 当我们鼠标经过 preview_img 就显示和隐藏 mask 遮挡层 和 big 大盒子
preview_img.addEventListener('mouseover', function() {
mask.style.display = 'block';
big.style.display = 'block';
})
preview_img.addEventListener('mouseout', function() {
mask.style.display = 'none';
big.style.display = 'none';
})
// 2. 鼠标移动的时候,让黄色的盒子跟着鼠标来走
preview_img.addEventListener('mousemove', function(e) {
// (1). 先计算出鼠标在盒子内的坐标
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
// console.log(x, y);
// (2) 减去盒子高度 300的一半 是 150 就是我们mask 的最终 left 和top值了
// (3) 我们mask 移动的距离
var maskX = x - mask.offsetWidth / 2; //保证鼠标在黄色遮罩层的 中间位置
var maskY = y - mask.offsetHeight / 2;
// (4) 限制黄色盒子的移动边框 不能超过preview_img.width 如果x 坐标小于了0 就让他停在0 的位置
// 遮挡层的最大移动距离
var maskMax = preview_img.offsetWidth - mask.offsetWidth; //照片的宽度 减去 黄色遮挡层的宽度
if (maskX <= 0) {
maskX = 0;
} else if (maskX >= maskMax) {
maskX = maskMax;
}
if (maskY <= 0) {
maskY = 0;
} else if (maskY >= maskMax) {
maskY = maskMax;
}
mask.style.left = maskX + 'px';
mask.style.top = maskY + 'px';
// 3. 大图片的移动距离 = 遮挡层移动距离 * 大图片最大移动距离 / 遮挡层的最大移动距离
// 大图
var bigIMg = document.querySelector('.bigImg');
// 大图片最大移动距离
var bigMax = bigIMg.offsetWidth - big.offsetWidth;
// 大图片的移动距离 X Y
var bigX = maskX * bigMax / maskMax;
var bigY = maskY * bigMax / maskMax;
bigIMg.style.left = -bigX + 'px'; //遮罩层与大图走的方向相反
bigIMg.style.top = -bigY + 'px';
})
})
4、详情页tab切换效果的实现
思路:
1、获取元素
2、先给每一个li添加一个索引号,在点击当前Li的时候利用排他思想(干掉其他人,只留下自己),然后获取当前Li的索引号,让下边对应的item[index]进行显示。
window.addEventListener("load", function() {
//先实现点击tab_list就会变红
var tab_list = document.querySelector('.detail_tab_list');
var lis = tab_list.querySelectorAll('li');
var tab_coin = this.document.querySelectorAll(".detail_tab_con")
var items = tab_coin.querySelectorAll('.item');
//console.log(tab_list);
//console.log(lis);
//console.log(items);
for (var i = 0; i < lis.length; i++) {
//要给每一个Li 添加一个索引号
lis[i].setAttribute('index',i); //注意此时i 是一个函数变量 不需要 加引号
lis[i].onclick = function() {
//利用排它思想 先干掉其他人 只留下自己
for(var i = 0; i < lis.length; i++) {
lis[i].className = '';
items[i].style.display = 'none';
}
//点击哪一个 哪一个就会变红
this.className = 'current';
// 下边的内容是跟着上边的 li 的变化而变化的 所以要取到当前li的索引号
var index = this.getAttribute('index');
//console.log(index);
//让content里边对应序号的内容显示出来 还是需要排他思想 但是把这段代码 放到了上边的排它代码里 简洁
/* for(var i = 0; i < items.length; i++) {
items[i].style.display = 'none';
} */
items[index].style.display = 'block';
}
}
})
<项目一难点>
1、轮播图中,点击下一张切换不过来的问题
解决方法:
设置节流阀:给一个flag = true;当点击下一张按钮的时候flag = false;直至动画结束之后才改成true。
2、详情页放大效果中大图片移动距离的确定
遮罩层的移动距离 / 遮罩层的最大移动距离 = 大图片的移动距离 / 大图片移动的最大距离
所以大图片的移动 = 遮罩层的移动距离 / 遮罩层的最大移动距离 * 大图片移动的最大距离
注意:遮罩层的移动方向和大照片的移动方向是相反的。
3、字体图标无法正常显示的问题
刚开始使用的是 外网的一个网站;但是本地服务器没有这种字体,所以使用了阿里巴巴的,就可以正常使用了。使用步骤如下:
1、登录阿里巴巴字体图标库 点击进入字体下载,之后选择自己想要的字体图标添加至购物车
2、将购物车中的字体图标添加至项目,然后点击font-class如果没有代码生成的话点击查看在线链接,就生成了代码,打开该代码,并且复制到响应HTML文档的style中
3、想要哪一个字体图标,就写哪一个类名,注意iconfont
类名是必须要写的。
<span class="iconfont icon-shouji1"></span>
<项目一的收获>
1、动态效果实现中layUI组件库和原生JS的不同
1、掌握了原生JS实现轮播图和插件实现轮播图,原生JS看中的更多的是代码的逻辑能力,框架更多的是代码的迁移能力。插件的话他要是改了就得在重新引入,原生的JS可以封装成一个函数,在不改变DOM元素类名的前提之下调用函数即可。
2、精灵图和字体图标的使用
<项目二>
项目过程
1、项目创建和github托管
1、先查看vscode有没有node环境 node -v
,如果没有的话从官网下载http://nodejs.cn/download/
与Windows对应的node版本即可,下载之后进行安装。安装成功的标志是 windows+R打开命令行之后输入 cmd 打开命令行,输入 node -v查看node版本。
2、安装 npm install vue
3、创建项目 vue create interviewer
,用的是vue-cli3创建的项目,脚手架3的文件配置位置:nodemodules中的vue中的cli-service中的lib中的config
4、这时候查看 package.json文件中的script行,进行npm run build
打包,npm run serve
运行项目,即可在页面中查看项目了。
5、在github上创建自己的项目,readme和gitingore都没有必要选,因为本地的项目中有。
6、建立远程和本地的联系。
注意>、如果本地项目中没有 .git文件夹的话,就 git init
一下就有了
1>、在本地项目中的同级的文件夹下git clone 远程仓库的位置
,这样就把远程仓库下载到本地了;之后在把interviewer中的内容复制到interviewer-network中;之后项目的编写在interviewer-network文件夹中进行,首先git status
查看文件状态,之后git add .
把所有的文件都加入到项目中,在git commit -m '项目介绍'
,在git push
推送到远程仓库。
2>、不用clone就可以建立连接。在远程建立一个空白仓库,在本地项目中首先 git remote add origin https://github.com/shixue1996/test.git
如果说仓库已存在的话就 git remote rm origin
,之后 git push -u origin master
,如果这里提示SSL错误的话,就git config --global http.sslVerify "false"
,之后在 git push -u origin master
就可以了。
2、划分目录结构
1、删除src中原有的内容
2、创建 assets用来存放 img 和 css ;components中划分成所有项目都可能用到的common组件和本项目中用到的context组件;common中用来存放公共的 js文件;network用来网络封装模块;router用来实现组件和路径之间的映射关系;store用来存放状态管理;view用来存放 home category等大的视图
3、引入基本的CSS文件
normalize.css文件作用:让不同的浏览器在渲染网页元素的时候样式更统一,比如有的浏览器规定超链接下边没有下划线有的有,在比如有的超链接是蓝色的有的是黑色的,这样可以对几乎所有的默认设置进行重置。
normalize.css的引入:在github上找到源文件进行引入。
base.css文件“就是引入了一些基本的样式。在base.css中导入@import "./normalize.css"
;并且在App.vue中导入@import "./assets/css/base.css";
4、配置别名和.editorconfig文件
别名的配置:因为在写文件路径的时候不用写一长串了。
新建一个vue.config.js
文件,
module.exports = {
//配置别名
configureWebpack: {
resolve: {
alias: {
'@': 'src',
'assets': '@/assets',
'common': '@/common',
'components': '@/components',
'network': '@/network',
'router': '@/router',
'store': '@/store',
'view': '@/view',
}
}
}
}
在一个就是.editorconfig
文件配置整个项目的代码规范,这个下载EditorConfig for VS Code插件才管用。
//告诉EditorConfig插件,这是根文件,不用继续往上查找
root = true
//匹配全部文件
[*]
//设置字符集
charset = utf-8
//缩进风格,可选"space"、"tab"
indent_style = tab
//缩进的空格数
indent_size = 2
//结尾换行符,可选"lf"、"cr"、"crlf"
end_of_line = lf
//在文件结尾插入新行
insert_final_newline = true
//删除一行中的前后空格
trim_trailing_whitespace = true
5、项目的模块划分通过tab-bar组件和路由的映射关系
在components中的common中放进tabbar文件夹,用来引入一个通用的组件。然后在context中放入tabbar文件夹的MainTabBar组件,然后在App.vue中导入注册使用该模块。因为tabbar模块需要路由的映射关系,所以就安装路由 npm install vue-router --save
,之后在router文件夹下的index.js文件中安装 创建 导出路由组件。
import Vue from 'vue'
import VueRouter from 'vue-router'
const Home = () => import('views/home/Home')
const Category = () => import('views/category/Category')
const Cart = () => import('views/cart/Cart')
const Profile = () => import('views/profile/Profile')
const Detail = () => import('views/detail/Detail')
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/category',
component: Category
},
{
path: '/cart',
component: Cart
},
{
path: '/profile',
component: Profile
},
{
path: '/detail',
component: Detail
}
],
mode: 'history'
})
export default router
6、首页的开发
1、小图标的修改
把title中的vue官网的图标换成自己的图标,在public文件夹中的index.html文件中决定了图标,<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <%= BASE_URL %>
这个是JSONP的语法,表示在当前文件路径下动态的获取该图标,打包到dist文件夹中之后就没有该句代码了。
2、首页导航栏的使用
首先封装一个 nav-bar的组件,
<template>
<div class="nav-bar ignore">
<div class="left"><slot name="left"></slot></div>
<div class="center"><slot name="center"></slot></div>
<div class="right"><slot name="right"></slot></div>
</div>
</template>
<script>
export default {
name: "NavBar"
}
</script>
<style scoped>
.nav-bar {
display: flex;
height: 44px;
line-height: 44px;
text-align: center;
}
.left {
width: 60px;
}
.right {
width: 60px;
}
.center {
flex: 1;
}
</style>
之后在views中的home文件夹下的Home.vue中导入 注册 使用该组件。使用的时候只用center这个中间的插槽,然后给该组件设置自己的样式。
3、先把首页中轮播图和十点开枪等数据从服务器拿过来
先安装网络封装模块 axios npm install axios
,之后在network文件夹中的network.js
文件中导入axios模块,之后创建网络请求,请求所有前端页面需要展示的数据。
import axios from 'axios'
// ES6 Promise的封装
export function request(options) {
return new Promise((resolve, reject) => {
// 1.创建axios的实例对象
const instance = axios.create({
baseURL: 'http://152.136.185.210:7878/api/m5',
timeout: 5000
})
// 过滤器(拦截器)
instance.interceptors.response.use(res => {
return res.data
})
// 通过实例发送网络请求
instance(options)
.then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
}
为了把不同页面展示的数据分开从network.js中请求,现在把首页性需要的数据放在network文件夹中的home.js文件中,
import {
request} from "./network";
//请求的是 轮播图和下边的数据
export function getMultiData() {
return request({
url: '/home/multidata'
})
}
//请求的是 下边所有的商品详情的数据
export function getProductData(type, page) {
return request({
url: '/home/data',
params: {
type,
page
}
})
}
从服务器请求过来的首页数据怎么才能在home.vue
中展示出来呢,就是在home.vue中导入一下这两个数据模块 这样的话 就是 home.vue面向home.js开发
import {
getMultiData, getProductData} from "network/home";
//从服务器中请求过来的首页需要展示的数据
请求过来的轮播图数据home.vue中的created中先调用一下请求函数,然后在methods做一下数据处理,请求过来的数据在data中做保存。
//data是用来保存 从服务器请求过来的数据的
data() {
return {
banners: [], /* 保存从服务器请求过来的数据 轮播图数据 */
recommends: [],
}
}
created() {
// 1.请求轮播等数据
this._getMultiData() /* 只需要调用方法 方法的实现放到 methods中 */
}
methods: {
/**
* 网络请求
*/
_getMultiData() {
getMultiData().then(res => {
/* 在箭头函数执行完毕之后,res会被回收,在回收之前把请求的数据res保存下来,这样的话 因为this指向该组件,banner属于组件的不会被回收 */
this.banners = res.data.banner.list
this.recommends = res.data.recommend.list
})
},
4、把轮播图数据在首页进行展示
在components中的common文件夹下引入封装好的组件swipper, 不抽的话接下来是这样的。在home.vue中导入封装好的组件import {Swiper, SwiperItem} from 'components/common/swiper'
然后注册组件,最后在template中使用组件。
<swiper>
//因为图片是由从服务器创建的数据动态生成的 所以要加一个动态绑定 :
<swiper-item v-for="(item, index) in banners" :key="index">
<!-- 动态的获取图片的链接 link这个数据是从请求的数据中看到的-->
<a :href="item.link">
<!-- 动态的获取轮播图中的图片 -->
<img :src="item.image" alt="">
</a>
</swiper-item>
</swiper>
但是如果把整个轮播图放在这里的话,后边的代码会越来越复杂,所以把这个轮播图抽出来,进行封装,放在home文件夹中的childcomps文件夹中,在该文件夹中建立HomeSwipper.vue文件,把刚才放在home.vue中的代码剪过来。
<template>
<swiper>
<swiper-item v-for="(item, index) in banners" :key="index">
<!-- 动态的获取图片的链接 -->
<a :href="item.link">
<!-- 动态的获取轮播图中的图片 -->
<img :src="item.image" alt="">
</a>
</swiper-item>
</swiper>
</template>
<script>
import {
Swiper, SwiperItem} from 'components/common/swiper'
export default {
name: "HomeSwiper",
//得从父亲那里 拿数据 啊
props: {
banners: {
type: Array,
required: true
}
},
components: {
Swiper,
SwiperItem
},
}
</script>
<style scoped>
</style>
然后在home.vue中导入该组件import HomeSwiper from './childComps/HomeSwiper',
然后注册一下,然后在template中使用<home-swiper :banners="banners" ></home-swiper>
5、获取推荐列表的数据
首先在 home.js中请求数据,
export function getMultiData() {
return request({
url: '/home/multidata'
})
}
然后在home.vue中导入该组件 然后在created中调用该函数在在methods中对该函数的数据进行操作,再把这些数据在箭头函数执行完毕销毁之前进行保存在data中
6、推荐数据的展示
在home文件夹的childcomps中封装一个组件RecommendView.vue
<template>
<div class="recommend">
<div class="recommend-item" v-for="(item, index) in recommends" :key="index">
<a :href="item.link">
//因为图片和标题都是可以被点击链接的,所以要包含在a标签中
<img :src="item.image" alt="">
<span>{
{
item.title}}</span>
</a>
</div>
</div>
</template>
<script>
export default {
name: "RecommendView",
//获取从 上一级请求过来的推荐数据
props: {
recommends: {
type: Array,
required: true
}
}
}
</script>
<style scoped>
//布局样式
.recommend {
display: flex;
margin-top: 10px;
font-size: 14px;
padding-bottom: 30px;
border-bottom: 10px solid #eee;
}
.recommend-item {
flex: 1;
text-align: center;
}
.recommend img {
width: 80px;
height: 80px;
margin-bottom: 10px;
}
</style>
然后在home.vue中导入该组件import RecommendView from './childComps/RecommendView'
然后注册,最后使用<recommend-view :recommends="recommends"></recommend-view>
组件接收数据的时候通过:recommends="recommends"
相当于用on来接收。
7、特征模块的封装
本质上就是一张图片
首先在home文件夹下的childcomps中封装一个组件FeatureView.vue,
<template>
<div class="feature">
<a href="https://act.mogujie.com/zzlx67">
<img src="~assets/img/home/recommend_bg.jpg" alt="">
</a>
</div>
</template