基于angular实现模拟微信小程序swiper组件
发布时间 - 2026-01-11 01:49:31 点击率:次这段时间的主业是完成一个家政类小程序,终于是过审核发布了。不得不说微信的这个小程序生态还是颇有想法的,抛开他现有的一些问题不说,其提供的组件系统乍一看还是蛮酷的。比如其提供的一个叫swiper的视图组件,就可以在写界面的时候省不少时间和代码,轮播图片跟可滑动列表都可以用。导致现在回来写angular项目时也想整一个这样的组件出来,本文就将使用angular的组件能力和服务能力完成这么一个比较通用,耦合度较低的swiper出来。

首先要选择使用的技术,要实现的是与界面打交道的东西,自然是实现成一个组件,最终要实现的效果是写下这样的代码就可以完成一个可以滑动的视图来:
<swipers> <swiper>视图1</swiper> <swiper>视图2</swiper> </swipers>
然后要把最基本的组件定义写出来,显然这里要定义两个组件。第一个是父级组件,选择器名字就叫ytm-swipers,目前做的事情仅仅是做一个外壳定义基本样式,使用时的子标签都会插入在ng-content标签中。
@Component({
selector: 'ytm-swipers',
template: `
<div class="view-body">
<ng-content></ng-content>
</div>
`,
styles: [`
.view-body{height: 100%;width: 100%;overflow: hidden;position: relative;}
`]
})
第二个就是子视图了,在父级组件下,每个子组件都会沾满父级组件,只有当前的子组件会显示,当切换视图时实际做的就是更改这些子组件的显示方式,说的最简单的话,这个子组件还是仅仅用来加一个子外壳,给外壳添加基本样式,实际的页面内容原封不动放在ng-content标签中。
@Component({
selector: 'swiper',
template: `
<div class="view-child" *ngIf="swiper.displayList.indexOf(childId) >= 0"
[ngClass]="{'active': swiper.displayList[0] === childId,
'prev': swiper.displayList[2] === childId, 'next': swiper.displayList[1] === childId}">
<ng-content></ng-content>
</div>
`,
styles: [`
.view-child{
height: 100%;width: 100%;position: absolute;top: 0;
transition: 0.5s linear;background: #fff;
overflow-x: hidden;
}
.view-child.active{left: 0;z-index: 9;}
.view-child.next{left: 100%;z-index: 7;}
.view-child.prev{left: -100%;z-index: 8;}
`]
})
下一步是要让这两个父子组件完成心灵的沟通,讲道理其实可以直接使用ElementRef强行取到DOM来操作,不过这里使用的是组件内服务。和普通的服务使用上没差别,不过其provider是声明在某个组件里的,所以此服务只有在此组件以及子组件中可以注入使用。
@Injectable()
class SwiperService {
public swiperList: number[];
public displayList: number[]; // 0为当前 1为下一个 2为上一个
public current: number;
private changing: boolean;
constructor() {
this.changing = false;
this.swiperList = [];
this.displayList = [];
this.current = 0;
}
public Add(id: number) {
this.swiperList.push(id);
switch (this.swiperList.length) {
case 1:
this.displayList[0] = id;
return;
case 2:
this.displayList[1] = id;
return;
default:
this.displayList[2] = id;
return;
}
}
public Next(): Promise<any> {
if (this.changing) {
return new Promise<any>((resolve, reject) => {
return reject('on changing');
});
}
this.changing = true;
let c = this.swiperList.indexOf(this.displayList[0]);
let n = this.swiperList.indexOf(this.displayList[1]);
let p = this.swiperList.indexOf(this.displayList[2]);
p = c;
c = n;
n = (c + 1) % this.swiperList.length;
this.displayList[0] = this.swiperList[c];
this.displayList[2] = this.swiperList[p];
this.displayList[1] = -1;
setTimeout(() => {
this.displayList[1] = this.swiperList[n];
this.changing = false;
}, 500);
return new Promise<any>((resolve, reject) => {
return resolve(this.displayList[0]);
});
}
public Prev(): Promise<any> {
if (this.changing) {
return new Promise<any>((resolve, reject) => {
return reject('on changing');
});
}
this.changing = true;
let c = this.swiperList.indexOf(this.displayList[0]);
let n = this.swiperList.indexOf(this.displayList[1]);
let p = this.swiperList.indexOf(this.displayList[2]);
n = c;
c = p;
p = p - 1 < 0 ? this.swiperList.length - 1 : p - 1;
this.displayList[0] = this.swiperList[c];
this.displayList[1] = this.swiperList[n];
this.displayList[2] = -1;
setTimeout(() => {
this.displayList[2] = this.swiperList[p];
this.changing = false;
}, 500);
return new Promise<any>((resolve, reject) => {
return resolve(this.displayList[0]);
});
}
public Skip(index: number): Promise<any> {
let c = this.swiperList.indexOf(this.displayList[0]);
if (this.changing || c === index) {
return new Promise<any>((resolve, reject) => {
reject('on changing or no change');
});
}
this.changing = true;
let n = (index + 1) % this.swiperList.length;
let p = index - 1 < 0 ? this.swiperList.length - 1 : index - 1;
this.displayList[0] = this.swiperList[index];
if (index > c) {
this.displayList[2] = this.swiperList[p];
this.displayList[1] = -1;
setTimeout(() => {
this.displayList[1] = this.swiperList[n];
this.changing = false;
}, 500);
return new Promise<any>((resolve, reject) => {
return resolve(this.displayList[0]);
});
} else {
this.displayList[1] = this.swiperList[n];
this.displayList[2] = -1;
setTimeout(() => {
this.displayList[2] = this.swiperList[p];
this.changing = false;
}, 500);
return new Promise<any>((resolve, reject) => {
return resolve(this.displayList[0]);
});
}
}
}
用到的变量包括: changing变量保证同时只能进行一个切换,保证切换完成才能进行下一个切换;swiperList装填所有的视图的id,这个id在视图初始化的时候生成;displayList数组只会有三个成员,装填的依次是当前视图在swiperList中的索引,下一个视图的索引,上一个视图的索引;current变量用户指示当前显示的视图的id。实际视图中的显示的控制就是使用ngClass指令来根据displayList和视图id附加相应的类,当前视图会正好显示,前一视图会在左边刚好遮挡,后一视图会在右边刚好遮挡。
同时服务还要提供几个方法:Add用于添加制定id的视图,Next用于切换到下一个视图(左滑时调用),Prev用于切换到前一个视图(右滑时调用),再来一个Skip用于直接切换到指定id的视图。
在子视图中注入此服务,需要在子视图初始化时生成一个id并Add到视图列表中:
export class YTMSwiperViewComponent {
public childId: number;
constructor(@Optional() @Host() public swiper: SwiperService) {
this.childId = this.swip
@Injectable()
class SwiperService {
public swiperList: number[];
public displayList: number[]; // 0为当前 1为下一个 2为上一个
public current: number;
private changing: boolean;
constructor() {
this.changing = false;
this.swiperList = [];
this.displayList = [];
this.current = 0;
}
public Add(id: number) {
this.swiperList.push(id);
switch (this.swiperList.length) {
case 1:
this.displayList[0] = id;
return;
case 2:
this.displayList[1] = id;
return;
default:
this.displayList[2] = id;
return;
}
}
public Next(): Promise<any> {
if (this.changing) {
return new Promise<any>((resolve, reject) => {
return reject('on changing');
});
}
this.changing = true;
let c = this.swiperList.indexOf(this.displayList[0]);
let n = this.swiperList.indexOf(this.displayList[1]);
let p = this.swiperList.indexOf(this.displayList[2]);
p = c;
c = n;
n = (c + 1) % this.swiperList.length;
this.displayList[0] = this.swiperList[c];
this.displayList[2] = this.swiperList[p];
this.displayList[1] = -1;
setTimeout(() => {
this.displayList[1] = this.swiperList[n];
this.changing = false;
}, 500);
return new Promise<any>((resolve, reject) => {
return resolve(this.displayList[0]);
});
}
public Prev(): Promise<any> {
if (this.changing) {
return new Promise<any>((resolve, reject) => {
return reject('on changing');
});
}
this.changing = true;
let c = this.swiperList.indexOf(this.displayList[0]);
let n = this.swiperList.indexOf(this.displayList[1]);
let p = this.swiperList.indexOf(this.displayList[2]);
n = c;
c = p;
p = p - 1 < 0 ? this.swiperList.length - 1 : p - 1;
this.displayList[0] = this.swiperList[c];
this.displayList[1] = this.swiperList[n];
this.displayList[2] = -1;
setTimeout(() => {
this.displayList[2] = this.swiperList[p];
this.changing = false;
}, 500);
return new Promise<any>((resolve, reject) => {
return resolve(this.displayList[0]);
});
}
public Skip(index: number): Promise<any> {
let c = this.swiperList.indexOf(this.displayList[0]);
if (this.changing || c === index) {
return new Promise<any>((resolve, reject) => {
reject('on changing or no change');
});
}
this.changing = true;
let n = (index + 1) % this.swiperList.length;
let p = index - 1 < 0 ? this.swiperList.length - 1 : index - 1;
this.displayList[0] = this.swiperList[index];
if (index > c) {
this.displayList[2] = this.swiperList[p];
this.displayList[1] = -1;
setTimeout(() => {
this.displayList[1] = this.swiperList[n];
this.changing = false;
}, 500);
return new Promise<any>((resolve, reject) => {
return resolve(this.displayList[0]);
});
} else {
this.displayList[1] = this.swiperList[n];
this.displayList[2] = -1;
setTimeout(() => {
this.displayList[2] = this.swiperList[p];
this.changing = false;
}, 500);
return new Promise<any>((resolve, reject) => {
return resolve(this.displayList[0]);
});
}
}
}
er.swiperList.length;
this.swiper.Add(this.swiper.swiperList.length);
}
}
这个id其实就是已有列表的索引累加,且一旦有新视图被初始化,都会添加到列表中(支持动态加入很酷,虽然不知道会有什么隐藏问题发生)。
父组件中首先必须要配置一个provider声明服务:
@Component({
selector: 'ytm-swipers',
template: `
<div class="view-body">
<ng-content></ng-content>
</div>
`,
styles: [`
.view-body{height: 100%;width: 100%;overflow: hidden;position: relative;}
`],
providers: [SwiperService]
})
然后就是要监听手势滑动事件,做出相应的切换。以及传入一个current变量,每当此变量更新时都要切换到对应id的视图去,实际使用效果就是:
<ytm-swipers [current]="1">...</ytm-swipers>可以将视图切换到id喂1的视图也就是第二个视图。
export class YTMSwiperComponent implements OnChanges {
@Input() public current: number;
@Output() public onSwiped = new EventEmitter<Object>();
private touchStartX;
private touchStartY;
constructor(private swiper: SwiperService) {
this.current = 0;
}
public ngOnChanges(sc: SimpleChanges) {
if (sc.current && sc.current.previousValue !== undefined &&
sc.current.previousValue !== sc.current.currentValue) {
this.swiper.Skip(sc.current.currentValue).then((id) => {
console.log(id);
this.onSwiped.emit({current: id, bySwipe: false});
}).catch((err) => {
console.log(err);
});
}
}
@HostListener('touchstart', ['$event']) public onTouchStart(e) {
this.touchStartX = e.changedTouches[0].clientX;
this.touchStartY = e.changedTouches[0].clientY;
}
@HostListener('touchend', ['$event']) public onTouchEnd(e) {
let moveX = e.changedTouches[0].clientX - this.touchStartX;
let moveY = e.changedTouches[0].clientY - this.touchStartY;
if (Math.abs(moveY) < Math.abs(moveX)) {
/**
* Y轴移动小于X轴 判定为横向滑动
*/
if (moveX > 50) {
this.swiper.Prev().then((id) => {
// this.current = id;
this.onSwiped.emit({current: id, bySwipe: true});
}).catch((err) => {
console.log(err);
});
} else if (moveX < -50) {
this.swiper.Next().then((id) => {
// this.current = id;
this.onSwiped.emit({current: id, bySwipe: true});
}).catch((err) => {
console.log(err);
});
}
}
this.touchStartX = this.touchStartY = -1;
}
}
此外代码中还添加了一个回调函数,可以再视图完成切换时执行传入的回调,这个使用的是angular的EventEmitter能力。
以上就是全部实现了,实际的使用示例像这样:
<ytm-swipers [current]="0" (onSwiped)="切换回调($event)">
<swiper>
视图1
</swiper>
<swiper>
视图2
</swiper>
<swiper>
视图3
</swiper>
</ytm-swipers>
视图的切换有了两种方式,一是手势滑动,不过没有写实时拖动,仅仅是判断左右滑做出反应罢了,二是更新[current]节点的值。
整个组件的实现没有使用到angular一些比较底层的能力,仅仅是玩弄css样式以及组件嵌套并通过服务交互,以及Input、Output与外界交互。相比之下ionic的那些组件就厉害深奥多了,笔者还有很长的路要走。
以上所述是小编给大家介绍的基于angular实现模拟微信小程序swiper组件,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言小编会及时回复大家的!
# angular
# swiper
# swiper在angularjs中使用循环轮播失效的解决方法
# Angular6中使用Swiper的方法示例
# 解决Angular.js中使用Swiper插件不能滑动的问题
# Angularjs中使用轮播图指令swiper
# AngularJS 中使用Swiper制作滚动图不能滑动的解决方法
# Angular引入swiper后autoplay失效的解决办法详解
# 的是
# 切换到
# 仅仅是
# 回调
# 会在
# 第二个
# 小编
# 就可以
# 为上
# 几个
# 会有
# 放在
# 第一个
# 都要
# 在此
# 一是
# 已有
# 列表中
# 两种
# 可以用
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
黑客如何通过漏洞一步步攻陷网站服务器?
Laravel如何处理和验证JSON类型的数据库字段
详解Nginx + Tomcat 反向代理 如何在高效的在一台服务器部署多个站点
,南京靠谱的征婚网站?
*服务器网站为何频现安全漏洞?
Laravel怎么生成二维码图片_Laravel集成Simple-QrCode扩展包与参数设置【实战】
php嵌入式断网后怎么恢复_php检测网络重连并恢复硬件控制【操作】
Laravel如何实现文件上传和存储?(本地与S3配置)
Windows驱动无法加载错误解决方法_驱动签名验证失败处理步骤
在Oracle关闭情况下如何修改spfile的参数
如何利用DOS批处理实现定时关机操作详解
奇安信“盘古石”团队突破 iOS 26.1 提权
JavaScript实现Fly Bird小游戏
Laravel怎么设置路由分组Prefix_Laravel多级路由嵌套与命名空间隔离【步骤】
手机网站制作平台,手机靓号代理商怎么制作属于自己的手机靓号网站?
使用spring连接及操作mongodb3.0实例
JavaScript如何操作视频_媒体API怎么控制播放
Laravel中的Facade(门面)到底是什么原理
Laravel观察者模式如何使用_Laravel Model Observer配置
网站页面设计需要考虑到这些问题
Laravel如何实现URL美化Slug功能_Laravel使用eloquent-sluggable生成别名【方法】
Laravel DB事务怎么使用_Laravel数据库事务回滚操作
香港代理服务器配置指南:高匿IP选择、跨境加速与SEO优化技巧
如何用PHP工具快速搭建高效网站?
如何在云指建站中生成FTP站点?
laravel怎么用DB facade执行原生SQL查询_laravel DB facade原生SQL执行方法
大连网站制作费用,大连新青年网站,五年四班里的视频怎样下载啊?
家族网站制作贴纸教程视频,用豆子做粘帖画怎么制作?
Laravel如何实现用户密码重置功能?(完整流程代码)
如何在腾讯云服务器上快速搭建个人网站?
如何在 Telegram Web View(iOS)中防止键盘遮挡底部输入框
如何快速搭建个人网站并优化SEO?
使用PHP下载CSS文件中的所有图片【几行代码即可实现】
创业网站制作流程,创业网站可靠吗?
佐糖AI抠图怎样调整抠图精度_佐糖AI精度调整与放大细化操作【攻略】
javascript如何操作浏览器历史记录_怎样实现无刷新导航
网站视频制作书签怎么做,ie浏览器怎么将网站固定在书签工具栏?
如何将凡科建站内容保存为本地文件?
如何在IIS中配置站点IP、端口及主机头?
详解阿里云nginx服务器多站点的配置
如何在腾讯云免费申请建站?
如何在服务器上三步完成建站并提升流量?
美食网站链接制作教程视频,哪个教做美食的网站比较专业点?
如何使用 jQuery 正确渲染 Instagram 风格的标签列表
详解Oracle修改字段类型方法总结
PythonWeb开发入门教程_Flask快速构建Web应用
Mybatis 中的insertOrUpdate操作
微信小程序 HTTPS报错整理常见问题及解决方案
高端建站如何打造兼具美学与转化的品牌官网?
黑客如何利用漏洞与弱口令入侵网站服务器?

