zoukankan      html  css  js  c++  java
  • 星级评分原理 N次重写的分析

    使用的是雪碧图,用的软件是CSS Sprite Tools

    第一次实现与分析:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>实现方式1</title>
    </head>
    <style>
        body,ul,li{
            padding: 0;
            margin: 0;
        }
        li{
            list-style-type: none;
            /* 1 */
        }
        .rating{
            width: 525px;
            height: 26px;
            margin: 100px auto;
        }
        .rating-item{
            float: left;
            height: 99px;
            width: 102px;
            background: url(imgs/img2.png) no-repeat;
        }
    </style>
    <body>
        <ul id="rating" class="rating">
            <li class="rating-item" title="很不好"></li>
            <li class="rating-item" title="不好"></li>
            <li class="rating-item" title="一般"></li>
            <li class="rating-item" title="好"></li>
            <li class="rating-item" title="很好"></li>
        </ul>
    
    <script src="http://libs.baidu.com/jquery/1.11.3/jquery.min.js"></script>
    <!-- 使用cdn的好处:与其他的网站共享 -->
    <script>
        // console.log($);
        var num = 2,
            $rating = $("#rating"),
            $item = $rating.find(".rating-item");//默认点亮
      //不足1:直接在全局作用域中命名污染了命名环境
    
        var lightOn = function(num){
            $item.each(function(index){//在each方法的函数中index是可以直接得到的
                console.log(index);
            if(index < num){
                $(this).css('background-position','0 -94px');//通过使用背景位置来调整,注意'0 0'与'0,0'是完全不一样的,今天会再开一篇写
                // console.log($(this).index(),num);
            }else{
                $(this).css('background-position','0 0');
            }
        });
    };
        //初始化
        lightOn(num);
      //不足:在每个图片上都绑定了监听事件,占用了过多资源,事件委托今天也会专门整理一篇
    // console.log(num); //事件绑定 $item.on('mouseover',function(){ lightOn($(this).index() + 1); }).on('click',function(){ num = $(this).index() + 1; }); $rating.on('mouseout',function(){ lightOn(num);//记住了选中之后的效果 }) </script> </body> </html>

    第二次重写:如果现在要多加一组星星评分在下面呢??(比如美团外卖)难道又要复制然后修改一下吗,我觉得不行

    使用了jQuery自制插件以及IIFE的一些知识

    
    <!--html部分只比上面多了一个id为rating2的父容器和五个星星-->
    <script src="http://libs.baidu.com/jquery/1.11.3/jquery.min.js"></script>
    <script>
        // console.log($);
    
        var rating =  (function(){//将这些变量和函数包裹在这个立即执行函数的块级作用域中,将外部会用到的返回出去
        //为了避免多次调用init多次声明lightOn,将其移出放到这,但又会有新的问题
        //$item访问不到了  要为其添加一个参数,使得在后面调用的时候$item是能被获取到的
        var lightOn = function($item,num){
            $item.each(function(index){
                console.log(index);
            if(index < num){ 
                $(this).css('background-position','0 -94px');
                // console.log($(this).index(),num);
            }else{
                $(this).css('background-position','0 0');
            }
        });
    };
        var init = function(el,num){//用函数字面量创建一个初始化函数init,包括了获取元素、点亮星星颗数的函数

    //这里需要注意了,用函数字面量定义的函数是不存在“提升”(hoisting)的,只有用函数声明定义的才具有这个特性
    var $rating = $(el), $item = $rating.find(".rating-item"); // //初始化,根据传入的元素和数值点亮默认的星星 lightOn($item,num); // console.log(num); //事件绑定 $rating.on('mouseover','.rating-item',function(){//在这里使用了事件代理写法,意指将.rating-item上的事件委托给$rating来处理 lightOn($item,$(this).index() + 1); }).on('click','.rating-item',function(){ num = $(this).index() + 1; }).on('mouseout',function(){ lightOn($item,num); } // jQuery插件,方便后面有多个类似需求时直接调用(这个算造轮子吗) $.fn.extend({ rating:function(num){//el就是下面的this return this.each(function(){ init(this,num);//this直接指向调用这个自定义方法的元素 }); } }); return{ init:init//将init方法返回出去 } })(); rating.init("#rating",2); // rating.init("#rating2",3); $("#rating2").rating(4);//调用自定义的插件

    第三种:这种开始我就有点脑壳疼了,使用了设计模式中的模板方法模式,虽然老师讲的很好,但是我总是半懂不懂...

        var rating = (function(){
            //点亮整颗的实现函数,后面还有实现半颗的
            var lightEntireOne = function(el,options){
                this.$el = $(el);
                this.$item = this.$el.find('.rating-item');
                this.opts = options;//注意这里options是对象
           //将传入的参数传递为调用该函数的元素(?)的属性    };
        //在这个函数的原型上添加方法,使所有实例都能共享   lightEntireOne.prototype.init
    = function(){ this.lightOn(this.opts.num); if(!this.opts.readOnly){//见下面的default属性,在这一次的重写中传入给初始化对象的不再是元素和一个数值
           //而是元素和一个对象,我们每次有需求变更,就在这个对象中进行调整,调整后的对象为options
           //这里的意思即是只有在options对象不是只读的,即可以修改,才可以对其绑定点击事件
           //比如豆瓣评分,平均评分是不可点击的,但个人评分可以~这样就完成了代码块的复用(?)
    this.bindEvent();} }; lightEntireOne.prototype.lightOn = function(num){ num = parseInt(num);//转为整型数 this.$item.each(function(index){ if(index < num){ $(this).css('background-position','0 -94px'); }else{ $(this).css('background-position','0 0'); } }); }; lightEntireOne.prototype.bindEvent = function(){ var current = this,//保存当前の图片 itemLength = current.$item.length;         //current.$el:当前元素的父元素(就是外面那层),this.$el = $(el) current.$el.on('mouseover','.rating-item',function(){ var currentNum = $(this).index() + 1;当前鼠标所在的序列值 console.log(currentNum); current.lightOn(currentNum);
              //(a)&&b,如果a真,则执行b
              //这里是避免后续添加新需求时又添加了其他名为select的...“东西” (
    typeof current.opts.select === 'function') && current.opts.select.call(this,currentNum ,itemLength);
              //注意这里的call方法,a.call(b,num1,...),把b用a的方法走一遍,但不做保存,这里的this即为五个子元素图片
              //而currentNum和itemLength即为后面传入select函数的参数 current.$el.trigger(
    'select',[currentNum,itemLength]);//!!!!!!   //在父元素上触发select事件,这个事件是自定义的 }).on('click','.rating-item',function(){ currentNum = $(this).index() + 1;//将要点亮的星星数值进行变化并保存在这个变量中 (typeof current.opts.chosen === 'function') && current.opts.chosen.call(this,currentNum ,itemLength); current.$el.trigger('chosen',[currentNum,itemLength]); }).on('mouseout',function(){、
              currentNum = $(this).index()+1; current.lightOn(currentNum); }); };
    //默认参数对象 var defaults = { num:0, readOnly:false, select:function(){},//默认情况下select和chosen不会触发任何事件 chosen:function(){} }; //初始化 var init = function(el,options){ //当用户没有传递参数时,使用默认的 options = $.extend({},defaults,options); //用options替代defaults,将生成的内容放到前面的空对象中,并将这个空对象返回赋值给options对象 new lightEntireOne(el,options).init(); }; return { init:init } })(); //下面主要是为了在鼠标移入和点击时在控制台打印出x/5的字样,在有需要时也可以通过ajax发送出去 rating.init("#rating",{ num:2, select:function(currentNum,total){ console.log(this);//lightEntireOne对象,要让其指向每颗星星,就要使用call console.log(currentNum + "/" + total); } });     //上面与下面两种方法都是可以的~ rating.init("#rating2",{ num:1, }); $("#rating").on('select',function(e,num,total){ console.log(num + "/" + total); }).on('chosen',function(e,num,total){ console.log(num + "/" +
    total); })    //注意在下面的预览中,在点击后this指向的是对应的子元素,如果不使用call方法,this指向的就是select()这个函数啦~

    打印的效果预览:

    ----2019-4-8 一更----

    ----4-10 二更----

    第四次重写,添加了半颗星的功能

    <script>
        var rating = (function(){
            //点亮整颗
            var lightEntireOne = function(el,options){
                this.$el = $(el);
                this.$item = this.$el.find('.rating-item');
                this.opts = options;
            };
            lightEntireOne.prototype.init = function(){
                this.lightOn(this.opts.num);
                if(!this.opts.readOnly){
                    this.bindEvent();}
            };
            lightEntireOne.prototype.lightOn = function(num){
                num = parseInt(num);
                this.$item.each(function(index){
                    if(index < num){
                        $(this).css('background-position','0 -94px');
                    }else{
                        $(this).css('background-position','0 0');
                    }
                });
            };      
            lightEntireOne.prototype.bindEvent = function(){
                var current = this,
                    itemLength = current.$item.length;
    
                current.$el.on('mouseover','.rating-item',function(){
                    var currentNum = $(this).index() + 1;
                    current.lightOn(currentNum);
                    (typeof current.opts.select === 'function') && current.opts.select.call(this,currentNum
                    ,itemLength);
                    current.$el.trigger('select',[current.opts.num,itemLength]);//!!!!!!
                
                }).on('click','.rating-item',function(){
                    current.opts.num = $(this).index() + 1;
                    (typeof current.opts.chosen === 'function') && current.opts.chosen.call(this,current.opts.num
                    ,itemLength);
                    current.$el.trigger('chosen',[current.opts.num,itemLength]);                
                }).on('mouseout',function(){
                    currentNum = $(this).index() + 1;
                    current.lightOn(current.opts.num);
    
                });
            };
    
            //
            var lightHalfOne = function(el,options){
                this.$el = $(el);
                this.$item = this.$el.find('.rating-item');
                this.opts = options;
                this.add = 0;
            };
            lightHalfOne.prototype.init = function(){
                this.lightOn(this.opts.num);
                if(!this.opts.readOnly){
                    this.bindEvent();}
            };
            lightHalfOne.prototype.lightOn = function(num){
                var count = parseInt(num),//向下舍入!只要整数部分
                    isHalf = count !== num;
                    console.log(count);
                this.$item.each(function(index){
                    if(index < count){
                        $(this).css('background-position','0 -94px');
                    }else{
                        $(this).css('background-position','0 0');
                    }
                });
    
                if(isHalf){
                    this.$item.eq(count).css('background-position','0 -186px');
                }
            };      
            lightHalfOne.prototype.bindEvent = function(){
                var current = this,
                    itemLength = current.$item.length;
                console.log(typeof(this),this);//lightHalfOne对象
    
                current.$el.on('mousemove','.rating-item',function(e){
                    var $this = $(this),
                    currentNum = 0;
                    console.log($this);
                    if(e.pageX - $this.offset().left < $this.width()/2){
                        //此时的this指向current.$el
                            current.add = 0.5;
                            // console.log("half");
                        }else{
                            current.add = 1;
                        }
                    currentNum = $this.index() + current.add;
                    current.lightOn(currentNum);
                    // console.log(currentNum);
                    (typeof current.opts.select === 'function') && current.opts.select.call(this,current.opts.num
                    ,itemLength);
                    console.log(currentNum);
                    console.log(current.opts.num);
                    current.$el.trigger('select',[current.opts.num,itemLength]);//!!!!!!
                
                }).on('click','.rating-item',function(){
                    current.opts.num = $(this).index() + current.add;;
                    (typeof current.opts.chosen === 'function') && current.opts.chosen.call(this,current.opts.num
                    ,itemLength);
                    current.$el.trigger('chosen',[current.opts.num,itemLength]);                
                }).on('mouseout',function(){
                    current.lightOn(current.opts.num);
    
                });
            };
    
            //默认参数
            var defaults = {
                mode:"lightEntire",
                num:0,
                readOnly:false,
                select:function(){},
                chosen:function(){}
            };
            //此mode非彼mode
            var mode = {
                'entire':lightEntireOne,
                'half':lightHalfOne
            }
    
            //初始化
            var init = function(el,options){
                 //当用户没有传递参数时,使用默认的
                 options = $.extend({},defaults,options);
                 if(!mode[options.mode]){
                     options.mode = 'entire';
                 }
                 //用options替代defaults,将生成的内容放到前面的空对象中,并将这个空对象返回
                // new lightRntire(el,options).init();
                // new lightHalfOne(el,options).init();
                new mode[options.mode](el,options).init();
            };
    
            return {
                init:init
            }
        })();
        
        rating.init("#rating",{
            mode:'entire',
            num:2.5,
            select:function(currentNum,total){
                // console.log(this);//lightEntireOne对象,要让其指向每颗星星,就要使用call
                console.log(currentNum + "/" + total);
            },
            chosen:function(currentNum,total){
                // console.log(this);//lightEntireOne对象,要让其指向每颗星星,就要使用call
                console.log(currentNum + "/" + total);
            }
        });
    
    
        rating.init("#rating2",{
            mode:'half',
            num:3.5,
        });
        $("#rating2").on('select',function(e,num,total){
            console.log(num + "/" + total);
        }).on('chosen',function(e,num,total){
            console.log(num + "/" + total);
        })
    
    </script>

    功能粗略分析

    1.当初始化没有传入mode或是不存在的mode属性时,mode值默认为entire,即不支持半星评分。

    2.以mode值为half为例,

    由于ishalf判断为true,所以会将第3颗星星(传入为2.5)变化到半颗的位置

    3.

    .

    这里的($)this是当前停留的那颗星星

     4.然后就是这个写法,相当于new entire(el,options).init();,中括号的写法会读取其值

    明天来更提取抽象父类的!

  • 相关阅读:
    005 ES的文档一些控制
    004 REST风格中在ES中的约定
    003 接触elasticsearch的Restful Api【快速入门】
    002 elasticsearch中的一些概念
    001 centos7下安装kibana
    000 centos7下安装elasticsearch7的单节点安装
    006 DOM节点操作与元素的创建
    006 认识BeanNameAware
    005 Spring和SpringBoot中的@Component 和@ComponentScan注解
    004 JpaRepository,CrudRepository,PagingAndSortingRepository的区别
  • 原文地址:https://www.cnblogs.com/linbudu/p/10670045.html
Copyright © 2011-2022 走看看