实现一个联系客服对话框的前端部分

  一直都想写一个对话框,正好公司买了一个,就照着外观自己也写一个,每次写都会碰到意想不到的情况,通过解决这些情况,就很好的了解和学习了js知识。
  先给出效果图:

      

    这一次主要是碰到了一个问题:极短时间内多次按Enter键触发”发送内容不能为空“的提示,提示也会多次触发渐隐效果,但实际上应该是出发一次,后来发现setTimeout()方法是有一个类似id的返回值(setInterval()方法也类似),可以用clearTimeout(id),将其停止。
    同时,也测试了一下键盘事件的发生顺序和可以获得的内容。输入框触发事件的顺序是focus-keydown-input-keyup-change-blur,在keydown发生的时候,能获得keycode,但是不能获得value;在input发生的时候,能获得value;在keyup发生的时候,能获得keycode,同时也能获得value,就是利用这个,实现了Enter键发送消息,shift+Enter换行。

    getElementByXX与querySelector(),querySelectorAll()还是有区别的,但是本文中getElementById()完全可以用后者替代。据我所知getElementById()性能上要比querySelector()快一些,而且getElementByXX是动态的查询,querySelectorAll()只能查询出HTML代码中的节点,不会考虑动态添加的。

    另外,操作DOM生成提问和回复的js代码写的不好,容我在搜索学习下=。=至少目前看来,createElement()之后应该马上appendChild()到父节点;可能一次性用innerHTML加入内容会更好。(2017年5月26日更新:今天在搜索关于回流与重绘的问题时候,突然想到了以前DOM笔记里的有关DocumentFragment类型的内容,毫无疑问,利用DocumentFragment类型来处理这种动态节点的添加再好不过了)

    下面给出代码:

    HTML & JS
  

  1 <!doctype html>
  2 <html>
  3 <head>
  4 <meta charset="utf-8">
  5 <title>客服聊天</title>
  6 <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
  7 <meta name="format-detection" content="telephone=no">
  8 <meta name="apple-mobile-web-app-capable" content="yes">
  9 <meta name="apple-mobile-web-app-status-bar-style" content="black">
 10 <link rel="stylesheet" href="styles/style.css">
 11 <script src="http://www.weizoom.com/static/resources/js/jquery-1.7.1.min.js"></script>
 12 </head>
 13 <body>
 14 <div class="dialogue-wrapper">
 15     <div id="btn_open" class="dialogue-support-btn">
 16         <i class="dialogue-support-icon"></i>
 17         <i class="dialogue-support-line"></i>
 18         <span class="dialogue-support-text">联系客服</span>
 19     </div>
 20     <div class="dialogue-main">
 21         <div class="dialogue-header">
 22             <i id="btn_close" class="dialogue-close">></i>
 23             <div class="dialogue-service-info">
 24                 <i class="dialogue-service-img">头像</i>
 25                 <div class="dialogue-service-title">
 26                     <p class="dialogue-service-name">XX客服</p>
 27                     <p class="dialogue-service-detail">XX客服支持平台</p>
 28                 </div>
 29             </div>
 30         </div>
 31         <div id="dialogue_contain" class="dialogue-contain">
 32             <p class="dialogue-service-contain"><span class="dialogue-text dialogue-service-text">您好,请提问</span></p>
 33             <!-- <p class="dialogue-customer-contain"><span class="dialogue-text dialogue-customer-text">我有个问题</span></p> -->
 34         </div>
 35         <div class="dialogue-submit">
 36             <p id="dialogue_hint" class="dialogue-hint"><span class="dialogue-hint-icon">!</span><span class="dialogue-hint-text">发送内容不能为空</span></p>
 37             <textarea id="dialogue_input" class="dialogue-input-text" placeholder="请输入您的问题,按Enter键提交(shift+Enter换行)"></textarea>
 38             <div class="dialogue-input-tools">
 39                 小工具预留位置
 40             </div>
 41         </div>
 42     </div>
 43 </div>
 44 <script>
 45     var doc = document;
 46     // 模拟一些后端传输数据
 47     var serviceData = {
 48         'robot': {
 49             'name': 'robot001',
 50             'dialogue': ['模拟回复1', '模拟回复2', '模拟回复3'],
 51             'welcome': '您好,robot001为您服务'
 52         }
 53     };
 54 
 55     var dialogueInput = doc.getElementById('dialogue_input'),
 56         dialogueContain = doc.getElementById('dialogue_contain'),
 57         dialogueHint = doc.getElementById('dialogue_hint'),
 58         btnOpen = doc.getElementById('btn_open'),
 59         btnClose = doc.getElementById('btn_close'),
 60         timer,
 61         timerId,
 62         shiftKeyOn = false;  // 辅助判断shift键是否按住
 63 
 64     btnOpen.addEventListener('click', function(e) {
 65         $('.dialogue-support-btn').css({'display': 'none'});
 66         $('.dialogue-main').css({'display': 'inline-block', 'height': '0'});
 67         $('.dialogue-main').animate({'height': '600px'})
 68     })
 69 
 70     btnClose.addEventListener('click', function(e) {
 71         $('.dialogue-main').animate({'height': '0'}, function() {
 72             $('.dialogue-main').css({'display': 'none'});
 73             $('.dialogue-support-btn').css({'display': 'inline-block'});
 74         });
 75     })
 76 
 77     dialogueInput.addEventListener('keydown', function(e) {
 78         var e = e || window.event;
 79         if (e.keyCode == 16) {
 80             shiftKeyOn = true;
 81         }
 82         if (shiftKeyOn) {
 83             return true;
 84         } else if (e.keyCode == 13 && dialogueInput.value == '') {
 85             // console.log('发送内容不能为空');
 86             // 多次触发只执行最后一次渐隐
 87             setTimeout(function() {
 88                 fadeIn(dialogueHint);
 89                 clearTimeout(timerId)
 90                 timer = setTimeout(function() {
 91                     fadeOut(dialogueHint)
 92                 }, 2000);
 93             }, 10);
 94             timerId = timer;
 95             return true;
 96         } else if (e.keyCode == 13) {
 97             var nodeP = doc.createElement('p'),
 98                 nodeSpan = doc.createElement('span');
 99             nodeP.classList.add('dialogue-customer-contain');
100             nodeSpan.classList.add('dialogue-text', 'dialogue-customer-text');
101             nodeSpan.innerHTML = dialogueInput.value;
102             nodeP.appendChild(nodeSpan);
103             dialogueContain.appendChild(nodeP);
104             dialogueContain.scrollTop = dialogueContain.scrollHeight;
105             submitCustomerText(dialogueInput.value);
106         }
107     });
108 
109     dialogueInput.addEventListener('keyup', function(e) {
110         var e = e || window.event;
111         if (e.keyCode == 16) {
112             shiftKeyOn = false;
113             return true;
114         }
115         if (!shiftKeyOn && e.keyCode == 13) {
116             dialogueInput.value = null;
117         }
118     });
119 
120     function submitCustomerText(text) {
121         console.log(text)
122         // code here 向后端发送text内容
123 
124         // 模拟后端回复
125         var num = Math.random() * 10;
126         if (num <= 7) {
127             getServiceText(serviceData);
128         }
129     }
130 
131     function getServiceText(data) {
132         var serviceText = data.robot.dialogue,
133             i = Math.floor(Math.random() * serviceText.length);
134         var nodeP = doc.createElement('p'),
135             nodeSpan = doc.createElement('span');
136         nodeP.classList.add('dialogue-service-contain');
137         nodeSpan.classList.add('dialogue-text', 'dialogue-service-text');
138         nodeSpan.innerHTML = serviceText[i];
139         nodeP.appendChild(nodeSpan);
140         dialogueContain.appendChild(nodeP);
141         dialogueContain.scrollTop = dialogueContain.scrollHeight;
142     }
143 
144     // 渐隐
145     function fadeOut(obj) {
146         var n = 100;
147         var time = setInterval(function() {
148             if (n > 0) {
149                 n -= 10;
150                 obj.style.opacity = '0.' + n;
151             } else if (n <= 30) {
152                 obj.style.opacity = '0';
153                 clearInterval(time);
154             }
155         }, 10);
156         return true;
157     }
158 
159     // 渐显
160     function fadeIn(obj) {
161         var n = 30;
162         var time = setInterval(function() {
163             if (n < 90) {
164                 n += 10;
165                 obj.style.opacity = '0.' + n;
166             } else if (n >= 80) {
167                 
168                 obj.style.opacity = '1';
169                 clearInterval(time);
170             }
171         }, 100);
172         return true;
173     }
174 </script>
175 </body>
176 </html>

    CSS

  

  1 @charset "utf-8";
  2 /*公共样式*/
  3 html{font-family:"Helvetica Neue",Helvetica,STHeiTi,sans-serif;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;}
  4 body{-webkit-overflow-scrolling:touch;margin:0;}
  5 ul{margin:0;padding:0;list-style:none;outline:none;}
  6 dl,dd{margin:0;}
  7 a{display:inline-block;margin:0;padding:0;text-decoration:none;background:transparent;outline:none;color:#000;}
  8 a:link,a:visited,a:hover,a:active{text-decoration:none;color:currentColor;}
  9 a,dt,dd{-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;}
 10 img{border:0;}
 11 p{margin:0;}
 12 input,button,select,textarea{margin:0;padding:0;border:0;outline:0;background-color:transparent;}
 13 /*css reset*/
 14 body {
 15     position: relative;
 16 }
 17 
 18 .dialogue-wrapper {
 19     font-size: 14px;
 20     color: #fff;
 21 }
 22 /*右侧点击按钮*/
 23 .dialogue-wrapper .dialogue-support-btn {
 24     position: fixed;
 25     display: inline-block;
 26     top: 50%;
 27     right: 0;
 28     margin-top: -70px;
 29     padding: 10px 0;
 30     width: 40px;
 31     height: 120px;
 32     font-size: 16px;
 33     font-weight: 500;
 34     text-align: center;
 35     cursor: pointer;
 36     border-top-left-radius: 5px;
 37     border-bottom-left-radius: 5px;
 38     box-shadow: -1px 1px 5px rgba(0, 0, 0, .4);
 39     background-color: #5d94f3;
 40 }
 41 
 42 .dialogue-wrapper .dialogue-support-btn .dialogue-support-icon {
 43     position: relative;
 44     display: inline-block;
 45     margin-bottom: -2px;
 46     width: 20px;
 47     height: 16px;
 48     border-radius: 4px;
 49     background-color: #fff;
 50 }
 51 
 52 .dialogue-wrapper .dialogue-support-btn .dialogue-support-icon:before {
 53     content: '';
 54     position: absolute;
 55     left: 50%;
 56     bottom: -6px;
 57     margin-left: -3px;
 58     width: 0;
 59     height: 0;
 60     border-left: 4px solid transparent;
 61     border-right: 4px solid transparent;
 62     border-top: 6px solid #fff;
 63 }
 64 
 65 .dialogue-wrapper .dialogue-support-btn .dialogue-support-line {
 66     display: inline-block;
 67     width: 100%;
 68     height: 1px;
 69     background-color: #ddd;
 70 }
 71 
 72 .dialogue-wrapper .dialogue-support-btn .dialogue-support-text {
 73     padding: 5px 0;
 74     letter-spacing: 4px;
 75     writing-mode: vertical-rl;
 76     -webkit-user-select: none;
 77 }
 78 
 79 /*底部客服对话框*/
 80 .dialogue-wrapper .dialogue-main {
 81     position: fixed;
 82     display: none;
 83     right: 100px;
 84     bottom: 10px;
 85     width: 400px;
 86     height: 600px;
 87     border-radius: 4px;
 88     box-shadow: 0 0 5px rgba(0, 0, 0, .4);
 89 }
 90 
 91 /*客服对话框头部*/
 92 .dialogue-wrapper .dialogue-main .dialogue-header {
 93     position: relative;
 94     padding: 10px;
 95     height: 80px;
 96     border-top-left-radius: 4px;
 97     border-top-right-radius: 4px;
 98     box-shadow: 0 0 5px rgba(0, 0, 0, .2);
 99     background-color: #5d94f3;
100 }
101 
102 .dialogue-wrapper .dialogue-main .dialogue-close {
103     position: absolute;
104     top: 10px;
105     right: 20px;
106     padding: 2px;
107     font-size: 22px;
108     transform: rotate(90deg);
109     cursor: pointer;
110 }
111 
112 .dialogue-wrapper .dialogue-main .dialogue-service-info {
113     position: relative;
114     top: 50%;
115     margin-top: -20px; 
116     height: 40px;
117 }
118 
119 .dialogue-wrapper .dialogue-main .dialogue-service-img {
120     display: inline-block;
121     margin: 0 10px 0 20px;
122     width: 40px;
123     height: 40px;
124     text-align: center;
125     line-height: 40px;
126     vertical-align: middle;
127     color: #000;
128     border-radius: 50%;
129     box-shadow: 1px 1px 4px rgba(0, 0, 0, .2);
130     background-color: #fff;
131 }
132 
133 .dialogue-wrapper .dialogue-main .dialogue-service-title {
134     display: inline-block;
135     vertical-align: middle;
136 }
137 
138 .dialogue-wrapper .dialogue-main .dialogue-service-detail {
139     font-size: 12px;
140 }
141 
142 /*客服对话框内容*/
143 .dialogue-wrapper .dialogue-main .dialogue-contain {
144     overflow-y: auto;
145     padding: 10px;
146     height: 380px;
147     word-wrap: break-word;
148     background-color: #f9f9f9;
149 }
150 
151 .dialogue-wrapper .dialogue-main .dialogue-text {
152     display: inline-block;
153     position: relative;
154     padding: 10px;
155     max-width: 120px;
156     white-space: pre-wrap;
157     border: 1px solid #09d07d;
158     border-radius: 4px;
159     background-color: #09d07d;
160     box-sizing: border-box;
161 }
162 
163 .dialogue-wrapper .dialogue-main .dialogue-service-contain {
164     margin-bottom: 10px;
165     text-align: left;
166 }
167 
168 .dialogue-wrapper .dialogue-main .dialogue-service-text {
169     margin-left: 20px;
170 }
171 
172 .dialogue-wrapper .dialogue-main .dialogue-service-text:before {
173     content: '';
174     position: absolute;
175     top: 50%;
176     left: -10px;
177     width: 0;
178     height: 0;
179     border-top: 6px solid transparent;
180     border-bottom: 6px solid transparent;
181     border-right: 10px solid #09d07d;
182     -webkit-transform: translate(0, -50%);
183     transform: translate(0, -50%);
184 }
185 
186 .dialogue-wrapper .dialogue-main .dialogue-customer-contain {
187     margin-bottom: 10px;
188     text-align: right;
189 }
190 
191 .dialogue-wrapper .dialogue-main .dialogue-customer-text {
192     margin-right: 20px;
193 }
194 
195 .dialogue-wrapper .dialogue-main .dialogue-customer-text:after {
196     content: '';
197     position: absolute;
198     top: 50%;
199     right: -10px;
200     width: 0;
201     height: 0;
202     border-top: 6px solid transparent;
203     border-bottom: 6px solid transparent;
204     border-left: 10px solid #09d07d;
205     -webkit-transform: translate(0, -50%);
206     transform: translate(0, -50%);
207 }
208 
209 /*客服对话框底部与输入*/
210 .dialogue-wrapper .dialogue-main .dialogue-submit {
211     position: relative;
212     padding: 10px;
213     height: 100px;
214     color: #000;
215     word-wrap: break-word;
216     border-top: 1px solid #ddd;
217     box-sizing: border-box;
218 }
219 
220 /*空输入提示*/
221 .dialogue-wrapper .dialogue-main .dialogue-hint {
222     position: absolute;
223     top: -15px;
224     left: 20px;
225     padding: 2px;
226     width: 140px;
227     height: 18px;
228     opacity: 0;
229     font-size: 12px;
230     text-align: center;
231     line-height: 18px;
232     border: 1px solid #ddd;
233     box-shadow: 1px 1px 4px rgba(0, 0, 0, .4);
234     background-color: #fff;
235 }
236 
237 .dialogue-wrapper .dialogue-main .dialogue-hint-icon {
238     display: inline-block;
239     width: 18px;
240     height: 18px;
241     margin-right: 5px;
242     font-size: 14px;
243     font-style: italic;
244     font-weight: 700;
245     vertical-align: middle;
246     line-height: 18px;
247     color: #fff;
248     border-radius: 50%;
249     background-color: #5d94f3
250 }
251 
252 .dialogue-wrapper .dialogue-main .dialogue-hint-text {
253     display: inline-block;
254     vertical-align: middle;
255 }
256 
257 /*输入框*/
258 .dialogue-wrapper .dialogue-submit .dialogue-input-text {
259     overflow-y: auto;
260     display: inline-block;
261     padding: 5px 10px;
262     width: 295px;
263     height: 70px;
264     vertical-align: middle;
265     white-space: pre-wrap;
266     word-wrap: break-word;
267     resize: none;
268     border-right: 1px solid #ddd;
269     box-sizing: border-box;
270 }
271 
272 .dialogue-wrapper .dialogue-submit .dialogue-input-tools {
273     display: inline-block;
274     width: 80px;
275     height: 80px;
276     vertical-align: middle;
277 }

   2017年6月1日补充:

  利用端午好好夯实了一下js的对象、原型链、继承等知识。

  代码中的渐隐、渐显完全可以扩展到Object的原型对象上:

  

 1     // 渐隐
 2     Object.prototype.iFadeOut = function() {
 3         var n = 100, 
 4             that = this;
 5         var time = setInterval(function() {
 6             if (n > 0) {
 7                 n -= 10;
 8                 that.style.opacity = '0.' + n;
 9             } else if (n <= 30) {
10                 that.style.opacity = '0';
11                 clearInterval(time);
12             }
13         }, 10);
14         return true;
15     }

  那么我们使用就看起来更为方便:node.iFadeOut()即可,例如dialogueHint.iFadeOut()。

posted @ 2017-05-14 19:12  悠悠洛  阅读(11098)  评论(3编辑  收藏  举报