用 TypeScript + Vue.js 打造一个渐现 Banner 组件

渐现 Banner,也是轮播图的一种。现在我们用 Vue.js 组件封装它,而且是 TypeScipt 语法的。本组件不依赖其他库或者函数。

用法如下:

<html>
	<head>
		<meta charset="utf-8" />
		<title>DEMO</title>
		<style type="text/css">
			/* AJAXJS Base CSS */
			body,dl,dt,dd,ul,li,pre,form,fieldset,input,p,blockquote,th,td,h1,h2,h3,h4,h5{margin:0;padding:0;}
			h1,h2,h3,h4,h5{font-weight: normal;}img{border:0;}ul li{list-style-type:none}.hide{display:none}
			body {-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing: grayscale;
				font-family: "Lantinghei SC", "Open Sans", Arial, "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", "STHeiti", "WenQuanYi Micro Hei", SimSun, sans-serif;}
			a{text-decoration:none;color:#666;transition:color .4s ease-in-out;}
			a:hover{color:#000;}
			button{border:none;outline:0;cursor:pointer;letter-spacing:2px;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none}
			input[type=password],input[type=text],select,textarea{outline:0;-moz-appearance:none;}
			
			/* 手机端浏览器所显示的网页 CSS */
			@media screen and (max-width:480px) {
				* {
					-webkit-tap-highlight-color: transparent; /* 很多 Android 浏览器的 a 链接有边框,这里取消它  */
					-webkit-touch-callout: none; /* 在 iOS 浏览器里面,假如用户长按 a 标签,都会出现默认的弹出菜单事件 */
					/* -webkit-user-select:none; */
				}
			}	
		</style> 
		<link rel="stylesheet" type="text/css" href="../common/main.css" />
		<script src="https://lib.baomitu.com/vue/2.6.11/vue.js"></script>
		<script src="../../dist/carousel/opacity-banner.js"></script>
	</head>
	<body>
		<div class="opacity-banner">
			<aj-opacity-banner>
				<li>
					<a href="#"> <img src="images/1.jpg" /></a>
				</li>
				<li>
					<a href="#"> <img src="images/2.jpg" /></a>
				</li>
				<li>
					<a href="#"> <img src="images/3.jpg" /></a>
				</li>
			</aj-opacity-banner>
		</div>

		<script type="text/javascript">
			new Vue({
				el: '.opacity-banner'
			});
		</script>
	</body>
</html>

<aj-opacity-banner> 包裹着的是含有图片的 <li></li> 标签。图片是 100% 宽度自适应的,见下面所用到的样式(基于 less.js)

.aj-opacity-banner{
	position: relative;
	
	li{
		position:absolute;
		top:0;
		left:0;
		opacity:0;
		width: 100%;
	}
	
	img{
		width: 100%;
	}
}

组件的属性如下。

属性 含义 类型 是否必填 默认值
delay 延时 Number n 3000
fps 帧速 Number n 25

用法如下:

<aj-opacity-banner delay="2000">
      ……
</aj-opacity-banner>

TypeScript 源码如下:

/**
 * 渐显 banner
 * 注意:定时器保存在 DOM 元素的属性上,是否会内存泄漏呢?
 */
; (() => {
    interface OpacityBanner extends Vue {
        active: number;

        timer: number;

        delay: number;

        fps: number;

        /**
         * 各帧
         */
        list: NodeListOf<HTMLLIElement>;

        states: [];

        /**
        * 内容淡出 
        */
        clear(): void;

        animate(params: number): void;

        run(): void;
    }

    Vue.component('aj-opacity-banner', {
        template: '<ul class="aj-opacity-banner"><slot></slot></ul>',
        props: {
            delay: { default: 3000 },   // 延时
            fps: { default: 25 }        // 帧速
        },
        data() {
            return {
                active: 0,  // 当前索引
            }
        },
        mounted(this: OpacityBanner): void {
            this.list = this.$el.querySelectorAll('li');
            this.list[0].style.opacity = "1";
            console.log(this.list.length);
            this.run();
        },
        methods: {
            /**
             * 播放动画
             * 
             * @param this 
             */
            run(this: OpacityBanner): void {
                this.timer = window.setInterval(() => {
                    var active: number = this.active;
                    this.clear();
                    active += 1;
                    this.active = active % this.list.length;
                    this.animate(100);
                }, this.delay);
            },

            /**
             * 下一帧
             * 
             * @param this 
             */
            per(this: OpacityBanner): void {
                var active = this.active;
                this.clear();
                active -= 1;
                active = active % this.list.length;

                if (active < 0)
                    active = this.list.length - 1;

                this.active = active;
                this.animate(100);
            },

            /**
             * 内容淡出 
             */
            clear(this: OpacityBanner): void {
                this.animate(0);
            },

            /**
             * 
             * @param this 
             * @param params 
             */
            animate(this: OpacityBanner, params: number): void {
                var el: HTMLLIElement = this.list[this.active], fps: number = 1000 / this.fps;
                window.clearTimeout(el.timer);

                window.setTimeout(function loop() {
                    var i: number = getOpacity(el);
                    var speed: number = (params - i) / 8, speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
                    // console.log("i=" + i + "; speed="+ speed+"; s="+s+"; k="+k);
                    i += speed;
                    el.style.opacity = String(i / 100);

                    window.clearTimeout(el.timer);
                    // params.complete && params.complete.call(elem);

                    el.timer = window.setTimeout(loop, fps);
                }, fps);
            }
        }
    });

    /**
     * 获取元首的透明度
     * 
     * @param el 
     */
    function getOpacity(el: Element): number {
        var v: number = Number(getComputedStyle(el)["opacity"]);
        v *= 100;

        return parseFloat(v + "") || 0;
    }
})();

原理上讲,就是为每张图片准备好定时器 timer,使其控制图片的透明度。当图片从透明度 0 到 100,就是渐现的过程;与此同时,另外一张图片由透明度 100 下降到 0。这两个过程是同时发生的,一个渐现一个渐隐,便会造成如此的目标效果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章