從零單排JavaScript第一期

故事背景


在很久很久以前……咳咳,倘若你已經接觸過其他語言,但和我一樣還沒有接觸過JavaScript,那咱們就一起到JavaScript的奇妙世界旅行吧。雖然這不是面向編程零基礎的童鞋們的文章,但是我會努力寫得讓這類童鞋也看得懂我們在做什麼,瞭解編程大致的流程到底是怎麼個樣子的(你們只用看代碼部分的中文註釋即可),如果把編程比喻爲攝影,那麼編程語言只是手中的那臺拍攝設備,有的人拿部手機就能拍出震撼人心的作品,而有的人即使拿臺萊卡拍出來的也只是...呵呵,所以真正的主角是你的洞察力,是你的腦海中形成的那個"畫面",就像拍攝前你至少得知道快門按鈕怎麼按,變焦又怎麼變(如果是用的變焦鏡頭的話)等等這些普適於基本所有相機的用法一般,編程語言也是如此,種類再多,但也萬變不離其宗,而這些東西我不會在文中一一介紹,因此說本系列文章不是面向編程零基礎的童鞋們的,但假若在和我們一起旅行的過程中你們能感到編程這件事本身是可愛迷人的反派角色,噢,不,平易近人的(不知道這個梗的,無視就好了:P),那就夠啦。文章將以記敘的方式展現沒接觸過JavaScript的我是如何用JavaScript一步步做出想要的那一個個web前端特效的。那麼首先先讓腦海中不斷涌現一個又一個有趣想法,然後再開始把它們一一實現出來!

好的~所以只要你知道餐廳沒有魚丸,也沒有粗麪,就不要再嘗試去點魚丸粗麪(不要吐槽怎麼又來一個梗啊摔,也不要吐槽假若有幾碗做好了沒賣出去之類奇奇怪怪的情況啊摔~),那就趕緊和我們一起出發吧。

p.s.由於主要講解關於JavaScript代碼部分的思路,所以HTML和CSS的代碼不會在文中貼出來,但會相應的提到一些相關問題。在最後我會給出Demo地址。

今日任務公告欄


爐石傳說中的那個匹配輪盤給我留下了深刻的印象,所以這次我打算做一個具備輪盤效果的摺疊列表。當鼠標移動到對應列上時就自動展開該列,處於漸變淡出的列不會被展開,屬於未激活狀態。需要通過鼠標滾輪轉動輪盤使漸變淡出的列滑動到焦點區域處於激活狀態,如圖1-1所示,當前處於激活狀態的列爲a列 b列 c列和d列,而e列 f列 g列爲未激活狀態。

啓程——投石問路


首先搭出列表的大致外觀,通過javascript來生成這個列表,代碼(僅javascript的代碼)如下:

 /*獲得Id爲panel的html標籤,這裏的id爲panel的標籤是一個div標籤,它是整個列表的父元素*/
  var panel = document.getElementById("panel");

  var html="";
  var num = 15; 

/*通過一個for循環生成列表的各列*/
  for(var i=0; i<num ;i++){ 
    html += "<div id=data"+i+" class='list'>"+i+"</div>";
  }

/*然後賦值給panel標籤,最終在頁面上顯示出來*/
  panel.innerHTML = html;

效果如下圖1-2所示。

接下來再考慮如何做出輪盤效果之前,我們需要看看javascript的鼠標滾輪事件是怎麼一回事的,在找度娘查詢一番後,得知javascript的滾輪事件在firefox中和其他如IE,Chrome等瀏覽器稍有不同(下面代碼中的scroll是我們再觸發滾輪後調用的函數),另外我只想讓這個列表響應鼠標滾輪事件(即當鼠標在列表上時滾輪纔有效果),所以在下面的代碼中用的是panel而不是document(此處的panel就是前面代碼中的變量panel),代碼如下:

/*在firefox中通過addEventListener來綁定firefox的滾輪事件DOMMouseScroll*/
if(document.addEventListener){
    panel.addEventListener('DOMMouseScroll',scroll,false);
}

/*在其它如IE,Chrome等瀏覽器中的滾輪事件爲onmousewheel,由於這類瀏覽器也支持addEventListener的事件綁定方法,所以此處就不要再寫else了*/
panel.onmousewheel = scroll;

此時當我們滾動鼠標滾輪時便會觸發scroll函數了,那麼現在的問題是,如何得知滾輪的滾動方向呢?關於滾動方向的獲取,firefox和其他如IE,Chrome等瀏覽器依然是不同的,firefox中是detail,而IE,Chrome等是wheelDelta。測試代碼如下:

function scroll(e){
    e = e || window.event;


/*IE,Opera,Chrome等瀏覽器*/
    if(e.wheelDelta){
      alert(e.wheelDelta);
    }


/*firefox*/
    else if(e.detail){ 
      alert(e.detail);
    }


}

經過我的測試,滾輪上滾時detail的值是-3,而wheelDelta是120,當滾輪下滾時detail的值是3,而wheelDelta是-120,可見不僅取值不同,而且同樣的滾動方向正負也不同。注意到那個變量e了嗎,剛開始的時候我是直接使用的event.detail和event.wheelDelta,這時候在有些瀏覽器中是獲取不到值的,這是因爲在不同的瀏覽器中的事件event和window.event是有區別的,所以爲了兼容性就寫成了上面代碼那樣。雖然問題解決了,但總覺得缺點什麼,那我們進一步探究探究event和window.event區別的吧,爲了方便,就直接把這個滾輪觸發後調用的scroll函數用來做測試吧,測試代碼如下:

function scroll(e){

   alert("e: "+e)
   alert("event: "+event);
   alert("window.event: "+window.event);

}

這裏特別要注意的一點是,IE瀏覽器自古以來一直和其他瀏覽器格格不入,說好聽點就是不隨主流有個性,說難聽點就是給開發人員填堵,直到後來的版本(是IE9還是IE10還是IE11,這個不清楚了,希望有知道的朋友能說一下:P),進行了很大程度的改變,而我這次用來測試的IE是IE11,所以結果極可能會和以前版本不一樣。另外我的chrome版本是36,firefox的版本爲31。

在Chrome中獲取到測試代碼中的這三個值皆爲[object WheelEvent],在IE11中這三個值皆爲[object  MouseWheelEvent],在firefox中首先會直接報錯提示event is not defined,也即未定義,至於e的值則爲[object MouseScrollEvent],但window.event的值爲undefined,也即沒有這個值。通過w3c的資料(點擊這裏打開關於w3c介紹window的頁面) 我們可知window表示的是一個全局變量,所以結論就是,目前IE和Chrome二者把事件對象作爲了全局變量來保存的,所以通過window.event能夠獲取到,同時也會把事件對象傳遞給對應的事件處理函數,所以在通過傳入函數的參數e,我們也能獲取到值,而firefox僅是通過把事件對象傳遞給對應的事件處理函數,並沒有把事件對象作爲一個全局對象,因此通過函數的傳入參數e能獲取到值,而通過window.event卻是undefined也即沒有這個值。至於那個單獨的event,我的推斷是它相當於是IE和Chrome等等瀏覽自己的一個對window.event的簡寫定義,因爲在firefox中調用event不是提示獲得值undefined而是直接瀏覽器報錯說event is not defined(如果我理解錯了,麻煩知道的朋友指正一下,謝謝:P)

瞭解了JavaScript中的鼠標滾輪事件之後,我們繼續來考慮輪盤效果的實現。

首先我想到的是把整個列表看作一個整體進行移動,如下圖1-3所示,黑框部分是整個列表,紅框內是列表可視部分,而在紅框外的通過CSS設置overflow屬性讓其隱藏掉,此時滾動滾輪時,讓黑框部分進行左或右的平移。當該列表中的第一列(從左往右數第一個方塊)進入紅框時,則把列表的最後一列(從左往右數最後一個方塊)放到第一個之前使其成爲整個列表的第一列。同理反向滾動讓列表最後一列進入紅框部分,在後面朝另一個方向滾動這部分情況就不再贅述了。

這個方案需要通過代碼獲得當前列表的第一列與紅框左邊框之間的距離值以及最後一列與紅框右邊框之間的距離值,然後是關於在紅框中的漸變效果,可以通過在紅框中增加兩個背景色爲白色的div層分別靠住左邊框和右邊框,其作用就是覆蓋作用,效果是一個向左漸變不透明顯出白色,另一個向右漸變不透明顯出白色,這兩個覆蓋層蓋住後便似乎是漸變透明消失了(當然前提得是在列表的背景色爲白色的情況下),然後被蓋住的部分鼠標移動上去也不會觸發列表展開的效果,只有中間沒有被蓋住的才行。這時候假若我展開了其中一個列表極有可能展開的內容會延長進入覆蓋層下面,導致這部分內容被覆蓋層蓋住形成變成漸變消失的視覺效果,因此除了計算好寬度距離之外,還需要把在會展開某列時就把某列的列移動到某位置,確保不會發生這樣的覆蓋情況,想到這裏有沒有覺得這個方法太費事了呢,那麼還有沒有其他方案來實現這個輪盤特效呢?

你們有其他的方法嗎?如果有的話,就留言分享一下吧:P,現在倒是我還有種辦法,我們可以通過設定id值(用class也行,但是在不依靠jq的情況下就純js的話,在獲取到具備對應的class屬性的標籤不如通過獲取id方便),比如說有四列我需要顯示出來,它們的id分別爲data0,data1,data2 ,data3,然後對這四個id設置CSS3中的transform屬性,使其爲函數translateX,接着通過該函數來對這些標籤進行一個從左往右的排列,而其它的列所屬的標籤則直接設置他們的CSS屬性display爲none就好了。當我滾動鼠標觸發事件函數時,就會對當前各列的id值進行替換,如下圖所示:

此時頁面上顯示的輪盤列表效果則爲前面的那幅圖1-2,當我滾動滾輪觸發事件函數時,該函數會對所有的列的id值重新賦值,如下圖:

這時候在頁面上顯示的輪盤列表效果是這樣的:

對各列id進行重新賦值的函數如下代碼所示:

  /*該函數爲當把輪盤從左往右轉動時所觸發的*/
function pre(){
    var list = panel.getElementsByTagName("div");
    var id;
    for(var i=0;i<num; i++){

/*id值爲data2這類形式,我們只需要對其中的數字部分進行處理,而在js中可以通過slice來對字符串進行分割*/
      id = parseInt(list[i].id.slice(4));
      id = id + 1;
      if(id >= num)
          id = 0;
      list[i].id = "data"+id;
    }
    return false;
  }

  /*該函數爲當把輪盤從右往左轉動時所觸發的*/
function next(){
    var list = panel.getElementsByTagName("div");
    var id;
    for(var i=0;i<num; i++){
      id = parseInt(list[i].id.slice(4));
      id = id - 1;
      if(id < 0)
          id = num-1;
      list[i].id = "data"+id;
    }
    return false;
  }

注意到上面代碼中的parseInt了嗎?由於javascript是弱類型語言,代碼中的id值又是從一個字符串中截取出來的內容爲數字的字符串,此時進行加法計算可能會出現比如這樣的情況:2+4 = 24,即把2和4看作了字符串結合在了一起,所以通過parseInt進行了一個轉換表明了這是一個數字。

接下來我們把滾輪事件的函數寫出來:

  function scroll(e){
    e = e || window.event;
   if(e.wheelDelta){//IE,Opera,Chrome
     if(e.wheelDelta > 0){
       pre();
     }
     else if(e.wheelDelta < 0){
       next();
     }
    }
    else if(e.detail){//firefox
     if(e.detail < 0){
       pre();
     }
     else if(e.detail > 0){
       next();
     }
    }
  }

好了現在當我們滾動鼠標滾輪的時候就能調用相應的函數去修改列表中各列的id值了。在運行的時候,我會發現有兩個問題,第一個問題就是由於各列默認情況下是沒有被設置translateX值的,只有需要顯示出來的列會通過translateX設定它們的位置取值,這樣的後果就是當我們進行滑動輪盤時會如下圖這樣列9和列8重合,原因就是列9是剛被賦值了具有顯示效果的id值,但列9本身是沒有translateX值的,這樣在被增加了一個translateX值後,它就直接會在所設定值的位置顯示出來(在下圖中也即列9會在列8本來所在的位置顯示出來),而此時列8正在向轉動輪盤之前的列7的位置移動中,一個直接在某位置顯示出來,一個正從某位置開始移動走,因此就出現了下圖這樣的重合效果,同理往另一個方向滑動輪盤就是最左邊的列出現重合效果。

這時候我們最先想到的解決辦法當然是把此時最左和最右的列設定它倆CSS屬性opacity的值爲0,讓它倆完全透明。

而第二個問題則是基於第一個問題的,假如我以極快的速度滑動滾輪過程中,這時候會出現如下圖這樣的情況:

停止滾動滾輪瞭然後列表才能恢復正常,上圖這時候我是在極快的滾動鼠標滾輪轉動方向是使輪盤從左往右滑動,這時候最右邊的列由於沒有了具備顯示效果的id值因此會直接消失,當我滾動速度快的時候,就會從最右邊開始很快的消失掉,而這時候左邊新的列由於有滑動的動畫效果於是還沒移動過去,因此形成了上圖這樣的效果。後來我給每個列都設置了translateX值後發現還是不行,於是把所有列都顯示出來,發現真正的原因是:當前顯示在頁面上的列表的最後一列都會在下一次滾動時直接挪動到第一列去,當極快速滾動滾輪時就還是前面說到的這個問題。所以現在看來似乎之前被我否決掉的那個第一種方案就不會出現這個問題。

如果不改變整個方案,也有些算不上解決辦法的辦法,比如我可以取消掉滾輪功能,改用按鈕的方式進行輪盤的滾動,對了,鼠標滾輪滾動的幅度很大的情況下,儘管手指只是滾動了一次,但其實相當於滾動了多次,一次大的滾動幅度是由多次小幅度的滾動組合而成的,在這樣的情況下也就不難理解爲什麼用滾輪這麼容易發生重合的情況,而人爲點擊按鈕可以最大程度避免了。

說到這裏不知道你們有沒有什麼想法,在這麼一分析滾輪的情況後,我直接找度娘詢問了一番,還真有一個人提到了相關的辦法,但他是用的jquery,可我現在就想用純js實現啊,不然怎麼好自稱是從零單排js,他的大概思路是給滾輪事件觸發時調用的函數裏設置一個延遲執行,然後在該延遲函數調用前進行一個獲取當前這個滾輪事件觸發函數是否有延遲函數在執行有的話就取消掉。而這樣做的效果就是,一次大幅度的鼠標滾輪滾動,在被細分爲各個小幅度的滾動後,實際上只有最後一個小幅度的滾動中的轉動輪盤函數有效。

所以接下來我們要解決的問題就是,如何通過js獲得當前所在函數的延遲函數,並把它取消掉。我想到了可以通過全局變量的形式來對延遲函數進行保存,見下代碼:

/*函數中的delayTime是一個初始值爲null的全局變量*/   

  function scroll(e){
  if(delayTime!=null){
     clearTimeout(delayTime);
   }

/*通過setTimeout讓函數在100毫秒後執行*/
delayTime = setTimeout(function(){
   e = e || window.event;
   if(e.wheelDelta){//IE,Opera,Chrome
     if(e.wheelDelta > 0){
       pre();
     }
     else if(e.wheelDelta < 0){
       next();
     }
    }
    else if(e.detail){//firefox
     if(e.detail < 0){
       pre();
     }
     else if(e.detail > 0){
       next();
     }
    }},100);
  }

到此爲止,整個特效就完成了一半了,接下來是鼠標移動到可以被展開的列上時就把該列展開,由於此時我們各列的position屬性爲absolute,所以此時僅使用CSS的hover是沒法把該列後邊的列撐開的,而只會覆蓋住。所以我們需要通過JS來對所展開的列的後邊的列進行一個位移動作。當鼠標移入的某個列後觸發javascript中鼠標的onmouseover事件,此時會根據當前鼠標移入的列進行判斷,因爲我之前設置的id爲data3,data4,data5的列是可被展開的,所以對當前所移入的列進行判斷,並執行相對應的動作即可。而鼠標移出是觸發的onmouseout事件,其觸發函數的功能同理於onmouseover事件的觸發函數。

/*鼠標移入某列觸發onmouseover事件所調用的函數*/
function mouseIn(){
   var thisId = this.getAttribute("id");
   var isdata3 = thisId=="data3" ? true:false;
   var isdata4 = thisId=="data4" ? true:false;
   var isdata5 = thisId=="data5" ? true:false;
   var objTag = document.getElementById(thisId);
   var buffer;
   if(isdata3){
     buffer = ["data4","data5","data6","data7","data8"];
     objTag.style.width = "100px";
   }

   else if(isdata4){
     buffer = ["data5","data6","data7","data8"];
     objTag.style.width = "100px";
   }

   else if(isdata5){
     buffer = ["data6","data7","data8"];
     objTag.style.width = "100px";
   }
   else{
     buffer = [];
   }
   for(var i=0;i<buffer.length;i++){
       var obj = document.getElementById(buffer[i]);
       var pos = parseInt(obj.style.left);
       pos += 80;
       obj.style.left = pos;
   }
  }

/*鼠標移出某列觸發onmouseout事件所調用的函數*/
function mouseOut(){
   var thisId = this.getAttribute("id");
   var isdata3 = thisId=="data3" ? true:false;
   var isdata4 = thisId=="data4" ? true:false;
   var isdata5 = thisId=="data5" ? true:false;
   var buffer;
   if(isdata3){
     buffer = ["data4","data5","data6","data7","data8"];
     document.getElementById(thisId).style.width = "40px";
   }

   else if(isdata4){
     buffer = ["data5","data6","data7","data8"];
     document.getElementById(thisId).style.width = "40px";
   }

   else if(isdata5){
     buffer = ["data6","data7","data8"];
     document.getElementById(thisId).style.width = "40px";
   }
   else{
     buffer = [];
   }
   for(var i=0;i<buffer.length;i++){
       var obj = document.getElementById(buffer[i]);
       var pos = parseInt(obj.style.left);
       pos -= 80;
       obj.style.left = pos;
   }
  }

然後通過CSS的僞類hover讓鼠標移動到可展開的列上時更改該列中顯示更多信息的span標籤的display屬性,讓其可見即可。

到這裏依然有個bug,就是當鼠標在處於展開列上時,滾動鼠標滑輪,這時候會對其他列的位置移動造成影響,也考慮過當鼠標滾輪滾動時進行一個檢查看是否右展開列,有的話,就執行一次鼠標移出時觸發的收縮展開列的函數,這個解決思路,也是存在缺陷的,仔細一想你就會發現,依然會導致後續列表的移位問題,原因就是當我滾動滑輪執行了一次收縮動作,但是這時候你只要稍微移動鼠標,便又會觸發一次onmouseout事件的觸發函數,我的推測就是在頁面上看雖然鼠標已經不在前面那個列中了,但是內存中的記錄卻是我的鼠標依舊還在前面那個列的對象上,這時候我稍微移動鼠標會觸發一次檢查發現當前鼠標已經不在之前那個列上了,所以又會調用一次onmouseout(如果我的理解有誤,希望知道的朋友能指正一下,謝謝:P)

關於這個問題暫時沒有什麼好的解決思路,另外一些美化貼圖之類的我就不再本系列文章中去做了,要不以後再開一個“從零單排web設計”的系列?哈哈,總之本期就先到這裏吧。

本期小結


在本期的從零單排javascript中,我們使用到了以下javascript的知識:

  • javascript中對html標籤的選擇方法,如getElementById,getElementsByTagName,getAttribute等等。
  • 鼠標滾輪事件的用法
  • 延遲函數settimeout的妙用
  • 鼠標移入和移出事件的用法

第一次一邊記錄一邊寫程序,現在回過頭來看,好些地方沒寫到位,沒描述清楚。在文章開頭我說會寫的讓編程零基礎的同學也看得懂我在做什麼,能在看完後瞭解編程的大致流程是什麼樣的,可是寫到一大半的時候,我發現這件事並沒有想象的那麼簡單,因爲總覺得不把一些基礎的東西先介紹介紹,就很難講明白,然後寫着寫着語言也開始生硬起來了。關於功能的總體實現思路,肯定有很多不足的地方,歡迎各位分享一下自己的思路看法:)

Demo演示地址請戳 >>> 傳送門

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