<dfn id="hx5t3"><strike id="hx5t3"><em id="hx5t3"></em></strike></dfn>

    <thead id="hx5t3"></thead><nobr id="hx5t3"><font id="hx5t3"><rp id="hx5t3"></rp></font></nobr>

    <listing id="hx5t3"></listing>

    <var id="hx5t3"></var>
    <big id="hx5t3"></big>

      
      

      <output id="hx5t3"><ruby id="hx5t3"></ruby></output>
      <menuitem id="hx5t3"><dfn id="hx5t3"></dfn></menuitem>

      <big id="hx5t3"></big>

        袁鈺涵

        袁鈺涵 查看完整檔案

        北京編輯  |  填寫畢業院校  |  填寫所在公司/組織 www.tvxinternet.com/u/yuanhan_5f5f19f9dabdc 編輯
        編輯

        而受苦又是一個壞習慣

        個人動態

        袁鈺涵 發布了文章 · 3月14日

        CSS 技巧集合 | 思否技術周刊

        CSS 是一種用來表現 HTML 或 XML 等文件樣式的計算機語言,相信朋友們對它都不陌生,今日給大家整理一個與 CSS?小技巧相關的合集,希望為大家提供一些設計新思路~

        CSS 有各種玩轉的方式,一起來看看吧~

        1、小技巧!CSS 整塊文本溢出省略特性探究

        今天的文章很有意思,講一講整塊文本溢出省略打點的一些有意思的細節。

        文本超長打點

        我們都知道,到今天(2021/03/06),CSS 提供了兩種方式便于我們進行文本超長的打點省略。

        對于單行文本,使用單行省略:

        {
        ????width:?200px;
        ????white-space:?nowrap;
        ????overflow:?hidden;
        ????text-overflow:?ellipsis;
        }
        

        而對于多行文本的超長省略,使用?-webkit-line-clamp 相關屬性,兼容性也已經非常好了:

        {
        ????width:?200px;
        ????overflow?:?hidden;
        ????text-overflow:?ellipsis;
        ????display:?-webkit-box;
        ????-webkit-line-clamp:?2;
        ????-webkit-box-orient:?vertical;
        }
        

        CodePen Demo -- inline-block 實現整塊的溢出打點

        https://codepen.io/Chokcoco/p...

        問題一:超長文本整塊省略

        問題二:iOS 不支持整塊超長溢出打點省略

        2、原來CSS的background還可以這么玩

        身為一個前端開發者,背景是開發中的???。大到整個網站的主題背景,小到一個按鈕的背景。CSS 的 background 屬性基本上每天開發都會遇到,絕大多數情況下我們都只會使用到了純色背景或者圖片背景。如果你想讓你開發的內容看起來更加生動有趣,通過本文讓你用純CSS也可以開發出炫酷的背景。

        開始之前

        在開始之前,先請你回答下面的問題,如果你能全部回答正確,說明你對 background 屬性掌握的還不錯哦!

        1.徑向漸變默認形狀是什么?

        A:原型 B:橢圓形

        1. background 屬性的值為多個時,哪個值的圖層在最頂部?

        A:第一個值 B:最后一個值

        1. background: green, linear-gradient(red, pink);?效果是什么?

        A:綠色背景 B:紅粉漸變背景 C:沒有背景

        1. 當background屬性有多個值時,如何指定每層背景的大???

        基礎背景

        首先還是先回顧一下基礎背景有哪些,最簡單的就是?純色背景:

        background:?pink;
        

        線性漸變,當然你還可以自定義方向:

        .linear?{
        ????background:?linear-gradient(red,?pink);
        }
        .linear1?{
        ????background:?linear-gradient(145deg,?red?20%,?pink);
        }
        

        徑向漸變

        background:?radial-gradient(red,?pink);
        

        角向漸變

        background:?conic-gradient(red,?pink);background:?conic-gradient(red,?pink);background:?radial-gradient(red,?pink);
        

        基礎背景擴展

        純色背景就沒什么可說的了,只能改變顏色。

        1、線性背景

        2、徑向背景

        3、角向漸變

        4、組合背景

        3、使用這些 CSS 屬性,布局效率又提高了一個層次!

        有很多CSS屬性,有些人不了解,或者他們了解它們,但是忘記在需要時使用它們。其實,有時候我們用 JavaScript 來實某些交互,CSS 一個屬性就能搞定了,這可以大大節約我們編碼的時間。

        作為前端開發人員,我們經常會遇到這樣的事情。所以我問自己,為什么不搞篇文章列出所有那些較少使用但既有用又有趣的 CSS 屬性?

        在本文中,我將介紹一些不一樣的CSS屬性,希望能給你帶來點新鮮感,廢話不多說,讓我們開始吧。

        在CSS網格中使用Place-Items

        我們只需使用兩行 CSS 代碼就可以將元素水平和垂直居中。

        HTML

        <div?class="hero">
        ????<div?class="hero-wrapper">
        ????????<h2>CSS?is?awesome</h2>
        ????????<p>Yes,?this?is?a?hero?section?made?for?fun.</p>
        ????????<a?href="#">See?more</a>
        ????</div>
        </div>
        

        CSS

        .hero?{
        ????display:?grid;
        ????place-items:?center;
        }
        

        place-items是將justify-items和align-items結合在一起的簡寫屬性。上面的代碼等同于下面代碼:

        .hero?{
        ????display:?grid;
        ????justify-items:?center;
        ????align-items:?center;
        }
        

        你可能想知道,這是怎么回事? 我們來解釋一下。當使用place-items時,它將應用于網格中的每個單元格,也就是說單元格的內容都會居中。如果我們多增加幾個單元格就會很清晰明了:

        .hero?{
        ????display:?grid;
        ????grid-template-columns:?1fr?1fr;
        ????place-items:?center;
        }
        

        Flexbox 與 margin 的配合

        與flexbox 結合使用,margin: auto 可以非常輕松地將 flex 項目水平和垂直居中。

        html

        <div?class="parent">
        ????<div?class="child"></div>
        </div>
        

        css

        .parent?{
        ????width:?300px;
        ????height:?200px;
        ????background:?#ccc;
        ????display:?flex;
        }
        .child?{
        ????width:?50px;
        ????height:?50px;
        ????background:?#000;
        ????margin:?auto;
        }
        

        看起來有點酷 ??

        列表的?marker?屬性

        text-align 屬性

        display: inline-Flex?屬性

        column-rule 屬性

        background-repeat: round

        object-fit 屬性

        4、資源:15 個優秀的響應式 CSS 框架

        響應式 Web 設計旨在為各種設備(從臺式機顯示器到手機)提供最佳的瀏覽體驗。本文匯總了一些優秀的響應式 Web 設計 HTML 和 CSS 框架。這些框架都是開源的并免費的。

        對響應式 Web 框架進行比較并不那么容易。有的框架適合設計更快、更精簡網站的某些功能,而有些可能提供了大量功能、插件和附加組件,但是可能體積會比較龐大并且上手較難。

        1. Bootstrap

        Bootstrap 是最流行的 HTML、CSS 和 JS 框架,用于在 Web 上開發響應式、移動優先項目。Bootstrap 使前端開發更快、更輕松。他們提供了大量的文檔、示例和演示,可以幫你快速進行響應式 Web 開發。在 Bootstrap 5 中做了一些重大更改,例如隨意使用 jQuery 并添加了 RTL 支持,再加上現成的組件和工具類,使 Bootstrap 成為 Web 開發人員的最佳選擇之一。

        你還可以找到許多免費的高級 bootstrap 模板?和 UI 工具包,這使你的開發過程更加輕松。

        官網:https://getbootstrap.com/

        2. Tailwind?CSS

        Tailwind 提供了一種基于實用工具的現代方法來構建響應站點。它有大量的實用工具類,無需編寫 CSS 即可構建現代網站。它與其它框架的不同之處在于需要通過開發設置來縮小最終 CSS 的大小,因為如果使用默認值,最終將會得到一個很大的 CSS 文件。Tailwind 能夠快速將樣式添加到 HTML 元素中,并提供了大量的開箱即用的設計樣式。這里有大量的 Tailwind CSS 資源:

        https://superdevresources.com...

        官網:https://tailwindcss.com/

        3. Tachyons

        Tachyons 也是一個基于實用工具的 CSS 庫,它提供了許多即裝即用的復雜功能,無需自己編寫大量 CSS。這樣做的好處是 Tachyons 的開箱即用樣式很輕巧,不需要其他設置。如果需要的話,仍然可以通過一些方法來減小尺寸。如果你需要易用的實用工具庫,那么這應該是一個不錯的選擇。

        官網:https://tachyons.io/

        4. Foundation

        Foundation 是由產品設計公司 ZURB 制作的自適應前端框架。這個框架是他們自 1998 年來構建 Web 產品和服務的結果。Foundation 是最先進的響應式前端框架,并且提供了許多自定義功能。

        官網:http://foundation.zurb.com/

        5. Material Design for Bootstrap (MDB)

        MDB 建立在 Bootstrap 之上,并提供了開箱即用的材料設計外觀。它具有出色的 CSS 庫,并且與大多數流行的 JavaScript 框架(如 jQuery、Angular、React 和。Vue.js)兼容。其核心庫是完全免費使用的。

        官網:https://mdbootstrap.com/

        1. UIkit
        2. Pure CSS
        3. Material Design Lite Framework (MDL)
        4. Materialize
        5. Skeleton
        6. Bulma
        7. Semantic UI
        8. Milligram
        9. Spectre.css
        10. Base CSS Framework

        5、使用 mask 實現視頻彈幕人物遮罩過濾

        經??匆恍?LOL 比賽直播的小伙伴,肯定都知道,在一些彈幕網站(Bilibili、虎牙)中,當人物與彈幕出現在一起的時候,彈幕會“巧妙”的躲到人物的下面,看著非常的智能。

        簡單的一個截圖例子:

        其實,這里是運用了 CSS 中的 MASK 屬性實現的。

        mask 簡單用法介紹

        之前在多篇文章都提到了 mask,比較詳細的一篇是 --?奇妙的 CSS MASK,本文不對 mask 的基本概念做過多講解,向下閱讀時,如果對一些 mask 的用法感到疑惑,可以再去看看。

        這里只簡單介紹下 mask 的基本用法:

        最基本,使用 mask 的方式是借助圖片,類似這樣:

        {
        ????/*?Image?values?*/
        ????mask:?url(mask.png);???????????????????????/*?使用位圖來做遮罩?*/
        ????mask:?url(masks.svg#star);?????????????????/*?使用?SVG?圖形中的形狀來做遮罩?*/
        }
        

        當然,使用圖片的方式后文會再講。借助圖片的方式其實比較繁瑣,因為我們首先還得準備相應的圖片素材,除了圖片,mask 還可以接受一個類似 background 的參數,也就是漸變。

        類似如下使用方法:

        {
        ????mask:?linear-gradient(#000,?transparent)??????????????????????/*?使用漸變來做遮罩?*/
        }
        

        那該具體怎么使用呢?一個非常簡單的例子,上述我們創造了一個從黑色到透明漸變色,我們將它運用到實際中,代碼類似這樣:

        下面這樣一張圖片,疊加上一個從透明到黑色的漸變,

        {
        ????background:?url(image.png)?;
        ????mask:?linear-gradient(90deg,?transparent,?#fff);
        }
        

        應用了 mask 之后,就會變成這樣:

        這個 DEMO,可以先簡單了解到 mask 的基本用法。

        這里得到了使用 mask 最重要結論:添加了 mask 屬性的元素,其內容會與 mask 表示的漸變的 transparent 的重疊部分,并且重疊部分將會變得透明。

        值得注意的是,上面的漸變使用的是 linear-gradient(90deg, transparent, #fff),這里的?#fff 純色部分其實換成任意顏色都可以,不影響效果。

        CodePen Demo -- 使用 MASK 的基本使用

        https://codepen.io/Chokcoco/p...

        使用 mask 實現人物遮罩過濾

        了解了 mask 的用法后,接下來,我們運用 mask,簡單實現視頻彈幕中,彈幕碰到人物,自動被隱藏過濾的例子。

        首先,我簡單的模擬了一個召喚師峽谷,以及一些基本的彈幕:

        方便示意,這里使用了一張靜態圖,表示了召喚師峽谷的地圖,并非真的視頻,而彈幕則是一條一條的?<p>?元素,和實際情況一致。偽代碼大概是這樣:

        <!--?地圖?-->
        <div?class="g-map"></div>
        <!--?包裹所有彈幕的容器?-->
        <div?class="g-barrage-container">
        ????<!--?所有彈幕?-->
        ????<div?class="g-barrage">6666</div>
        ????...
        ????<div?class="g-barrage">6666</div>
        </div>
        

        為了模擬實際情況,我們再用一個 div 添加一個實際的人物,如果不做任何處理,其實就是我們看視頻打開彈幕的感受,人物被視頻所遮擋:

        注意,這里我添加了一個人物亞索,并且用 animation 模擬了簡單的運動,在運動的過程中,人物是被彈幕給遮擋住的。

        接下來,就可以請出 mask 了。

        我們利用 mask 制作一個 radial-gradient ,使得人物附近為 transparent,并且根據人物運動的 animation,給 mask 的 mask-position 也添加上相同的 animation 即可。最終可以得到這樣的效果:

        .g-barrage-container?{
        ????position:?absolute;
        ????mask:?radial-gradient(circle?at?100px?100px,?transparent?60px,?#fff?80px,?#fff?100%);
        ????animation:?mask?10s?infinite?alternate;
        }
        @keyframes?mask?{
        ????100%?{
        ????????mask-position:?85vw?0;
        ????}
        }
        

        實際上就是給放置彈幕的容器,添加一個 mask 屬性,把人物所在的位置標識出來,并且根據人物的運動不斷的去變換這個 mask 即可。我們把 mask 換成 background,原理一看就懂。

        • 把 mask 替換成 background 示意圖:

        background 透明的地方,即 mask 中為 transparent 的部分,實際就是彈幕會被隱藏遮罩的部分,而其他白色部分,彈幕不會被隱藏,正是完美的利用了 mask 的特性。

        其實這項技術和視頻本身是無關的,我們只需要根據視頻計算需要屏蔽掉彈幕的位置,得到相應的 mask 參數即可。如果去掉背景和運動的人物,只保留彈幕和 mask,是這樣的:

        需要明確的是,使用 mask,不是將彈幕部分給遮擋住,而是利用 mask,指定彈幕容器之下,哪些部分正常展示,哪些部分透明隱藏。

        最后,完整的 Demo 你可以戳這里:

        CodePen Demo -- mask 實現彈幕人物遮罩過濾點擊預覽

        https://codepen.io/Chokcoco/p...

        實際生產環境中的運用

        當然,上面我們簡單的還原了利用 mask 實現彈幕遮罩過濾的效果。但是實際情況比上述的場景復雜的多,因為人物英雄的位置是不確定的,每一刻都在變化。所以在實際生產環境中,mask 圖片的參數,其實是由后端實時對視頻進行處理計算出來的,然后傳給前端,前端再進行渲染。

        對于運用了這項技術的直播網站,我們可以審查元素,看到包裹彈幕的容器的 mask 屬性,每時每刻都在發生變化:

        返回回來的其實是一個 SVG 圖片,大概長這個樣子:

        這樣,根據視頻人物的實時位置變化,不斷計算新的 mask,再實時作用于彈幕容器之上,實現遮罩過濾。

        最后

        本文到此結束,希望對你有幫助 :),本文介紹了 CSS mask 的一個實際生產環境中,非常有意義的一次實踐,也表明很多新的 CSS 技術,運用得當,還是能給業務帶來非常有益的幫助的。


        image.png

        查看原文

        贊 22 收藏 15 評論 1

        袁鈺涵 贊了文章 · 3月11日

        Babel7 相關

        文章首發于個人github blog: Biu-blog,歡迎大家關注~

        @babel/preset-env

        @babel/preset-env 主要的功能是依據項目經過 babel 編譯構建后產生的代碼所對應運行的目標平臺。@babel/preset-env 內部依賴了很多插件: @babel/plugin-transform-*。這些插件的工作主要就是 babel 在處理代碼的過程當中對于新的 ES 語法的轉換,將高版本的語法轉化為低版本的寫法。例如 @babel/plugin-transform-arrow-function 是用來轉化箭頭函數語法的。

        基本的配置方法:

        // babel.config.json
        {
          "presets": [
            [
              "@babel/preset-env",
              {
                // 相關 preset 的配置
              }
            ]
          ]
        }

        對于 web 側的項目或者 基于 Electron 的項目,一般會搭配著 .browserlistrc (或 package.json 里的 browserslist 字段) 來使用(確定最終構建平臺)。

        相關 options 配置

        useBuiltIns

        "usage" | "entry" | false, defaults to false.

        這個配置選項也決定了 @babel/preset-env 如何去引用 polyfills。當這個配置選項為:usageentry,@babel/preset-env 會直接建立起對于 core-js 相關 module 的引用。因此這也意味著 core-js 會被解析為對應的相對路徑同時需要確保 core-js 在你的項目當中已經被安裝了。

        因為從 @babel/polyfill 從 7.4.0 版本開始就被棄用了,因此推薦直接配置 corejs 選項,并在項目當中直接安裝 core-js。

        useBuiltIns: 'entry'

        使用這種方式的配置需要在你的業務代碼當中注入:

        import 'core-js/stable'
        import 'regenerator-runtime/runtime'

        babel 處理代碼的過程當中,會引入一個新的插件,同時 @babel/preset-env 會根據目標平臺,例如 target 當中的配置,或者是 .browserlistrc 等來引入對應平臺所需要的 polyfill

        In:

        import 'core-js'

        Out(different based on environment):

        import "core-js/modules/es.string.pad-start"
        import "core-js/modules/es.string.pad-end"

        注:其實這里的 useBuiltIns: entry 的配置以及需要在業務代碼當中需要注入 core-jsregenerator-runtime/runtime,在業務代碼當中注入對應的 package 從使用上來講更多的是起到了占位的作用,由 @babel/preset-env 再去根據不同的目標平臺去引入對應所需要的 polyfill 文件

        同時在使用的過程中,如果是 import 'core-js' 那么在處理的過程當中會引入所有的 ECMAScript 特性的 polyfill,如果你只希望引入部分的特性,那么可以:

        In:

        import 'core-js/es/array'
        import 'core-js/proposals/math-extensions'

        Out:

        import "core-js/modules/es.array.unscopables.flat";
        import "core-js/modules/es.array.unscopables.flat-map";
        import "core-js/modules/esnext.math.clamp";
        import "core-js/modules/esnext.math.deg-per-rad";
        import "core-js/modules/esnext.math.degrees";
        import "core-js/modules/esnext.math.fscale";
        import "core-js/modules/esnext.math.rad-per-deg";
        import "core-js/modules/esnext.math.radians";
        import "core-js/modules/esnext.math.scale";
        useBuiltIns: 'usage'

        自動探測代碼當中使用的新的特性,并結合目標平臺來決定引入對應新特性的 polyfill,因此這個配置是會最大限度的去減少引入的 polyfill 的數量來保證最終生成的 bundler 體積大小。

        不過需要注意的是:由于 babel 處理代碼本來就是一個非常耗時的過程,因此在我們實際的項目當中一般是對于 node_modules 當中的 package 進行 exclude 配置給忽略掉的,除非是一些明確需要走項目當中的 babel 編譯的 package 會單獨的去 include,所以 useBuiltIns: 'usage' 這種用法的話有個風險點就是 node_modules 當中的第三方包在實際的編譯打包處理流程當中沒有被處理(例如有些 package 提供了 esm 規范的源碼,同時 package.json 當中也配置了 module 字段,那么例如使用 webpack 這樣的打包工具的話會引入 module 字段對應的入口文件)

        同時,如果使用 useBuiltIns: 'usage' 配置的話。是會在每個文件當中去引入相關的 polyfill 的,所以這里如果不借助 webpack 這種打包工具的話,是會造成代碼冗余的。

        useBuiltIns: false

        Don't add polyfills automatically per file, and don't transform import "core-js" or import "@babel/polyfill" to individual polyfills.

        corejs

        corejs 的配置選項需要搭配著 useBuiltIns: usageuseBuiltIns: entry 來使用。默認情況下,被注入的 polyfill 都是穩定的已經被納入 ECMAScript 規范當中的特性。如果你需要使用一些 proposals 當中的 feature 的話,那么需要配置:

        {
          "presets": [
            [
              "@babel/preset-env",
              {
                // 相關 preset 的配置
                corejs: {
                  version: 3,
                  proposals: true
                }
              }
            ]
          ]
        }

        @babel/plugin-transform-runtime

        出現的背景:

        Babel 在編譯處理代碼的過程當中會使用一些 helper 輔助函數,例如 _extend。這些輔助函數一般都會被添加到每個需要的被處理的文件當中。

        因此 @babel/plugin-transform-runtime 所要解決的問題就是將所有對于需要這些 helper 輔助函數的引入全部指向 @babel/runtime/helpers 這個 module 當中的輔助函數,而不是給每個文件都添加對應 helper 輔助函數的內容。

        另外一個目的就是去創建一個沙盒環境。因為如果你直接引入 core-js,或者 @babel/polyfill 的話,它所提供的 polyfill,例如 Promise,Set,Map 等,是直接在全局環境下所定義的。因此會影響到所有使用到這些 API 的文件內容。所以如果你是寫一個 library 的話,最好使用 @babel/plugin-transform-runtime 來完成相關 polyfill 的引入,這樣能避免污染全局環境。

        這個插件所做的工作其實也是引用 core-js 相關的模塊來完成 polyfill 的功能。最終所達到的效果和使用 @babel/polyfill 是一樣的。

        配置方法:

        {
          "plugins": [
            [
              "@babel/plugin-transform-runtime",
              {
                "corejs": 3
              }
            ]
          ]
        }

        The plugin defaults to assuming that all polyfillable APIs will be provided by the user. Otherwise the corejs option needs to be specified.

        需要注意的是不同 corejs 版本提供的 helpers 有一些功能上的差異:corejs: 2 僅支持全局的定義,例如 Promise,和一些靜態方法,例如 Array.from,實例上的方法是是不支持的,例如 [].includes。不過 corejs: 3 是支持實例上的方法的。

        默認情況下,@babel/plugin-transform-runtime 是不會引入對于 proposals 的 polyfill 的,如果你是使用 corejs: 3 的話,可以通過配置 proposal: true 來開啟這個功能。

        corejs optionInstall command
        falsenpm install --save @babel/runtime
        2npm install --save @babel/runtime-corejs2
        3npm install --save @babel/runtime-corejs3

        技術實現細節

        The transform-runtime transformer plugin does three things:

        1. Automatically requires @babel/runtime/regenerator when you use generators/async functions (toggleable with the regenerator option).
        2. Can use core-js for helpers if necessary instead of assuming it will be polyfilled by the user (toggleable with the corejs option)
        3. Automatically removes the inline Babel helpers and uses the module @babel/runtime/helpers instead (toggleable with the helpers option).

        What does this actually mean though? Basically, you can use built-ins such as Promise, Set, Symbol, etc., as well use all the Babel features that require a polyfill seamlessly, without global pollution, making it extremely suitable for libraries.

        Some tips

        1. 如果使用 @babel/preset-envuseBuiltIns: usage 搭配 browserlist 的這種 polyfill 的方式的話,polyfill 是會污染全局的(entry 模式也是污染全局)。不過這種配置的方式會依據目標打包平臺來一定程度上減少不需要被加入到編譯打包流程的 polyfill 的數量,因此這種方式也對應的能較少 bundle 最終的體積大小。
        2. 如果是走 @babel/plugin-transform-runtime 插件的 polyfill 的話不會污染全局。但是這個插件沒法利用 browserlist 的目標平臺配置的策略。因此在你代碼當中只要是使用了 ES6+ 的新 api,一律都會引入對應的 polyfill 文件(而不考慮這個新的 api 是否被目標瀏覽器已經實現了),這樣也會造成 bundle 體積增大。針對這個問題,官方也嘗試提供一個新的 babel-polyfills package,以及策略去解決類似的問題。詳見對應的文檔以及issue

        相關文檔

        1. @babel/plugin-transform-runtime
        2. polyfill還是transform-runtime
        查看原文

        贊 5 收藏 2 評論 0

        袁鈺涵 贊了文章 · 3月10日

        與你項目相關的npm知識總結

        每次克隆下別人的代碼后,執行的第一步就是 npm install 安裝依賴包,安裝成功后所有的包都會放在項目的 node_modules 文件夾下,也會自動生成 package-lock.json文件。有沒有好奇過 node_modules 下的文件都是啥?package-lock.json 文件的作用是啥?

        本文主要解決以下幾個問題:

        1. package.json中的 dependenciesdevDependencies 的區別是啥,peerDependencies、bundledDependencies、optionalDependencies又是啥?
        2. 為什么有的命令寫在 package.json 中的 script 中就可以執行,但是通過命令行直接執行就不行?
        3. 為什么需要 package-lock.json 文件?
        4. 一個包在項目中有可能需要不同的版本,最后安裝到根目錄 node_modules 中的具體是哪個版本?

        帶著這幾個問題,我們先從 package.json 文件說起。

        package.json

        最靠譜的官方文檔請點這里

        官方文檔中列出了好多屬性,感興趣的可以一個個看一遍。下面只列出其中幾個比較常用且重要的屬性。

        name & version

        如果想要發布一個 npm 包,nameversion 屬性是必須的。他們兩個組合會形成一個唯一的標識來表名當前包。以后每更新一次包,version 就需要進行相應的更改。如果你不打算發布包,只想在本地使用,這兩個字段不是必須的。

        name 字段命名的規則如下:

        • 長度不能超過214個字符(對于有scoped的包,該限制包括scoped字段)(什么是Scoped packages?
        • 有作用域的包名字可以以.或者_開頭,沒有作用域限制的不可
        • 不能含有大寫字母
        • 不能含有非URL安全的字符

        version字段

        版本號需要符合 semver(語義化版本號)規則,具體版本格式為:主版本號.次版本號.修訂號, 如1.1.0。

        • 主版本號(major):做了不兼容的 API 修改
        • 次版本號(minor):做了向下兼容的功能性新增
        • 修訂號(patch):做了向下兼容的問題修正

        當有一些先行版本需要發布時,可以在 主版本號.次版本號.修訂號 之后加上一個中劃線和標識符如alpha(內部版本)、beta(公測版本)、rc(候選版本)等來表明。

        以vue的版本為例:

        • 最新的穩定版本:3.0.5
        • 最新的rc版本:3.0.0-rc.13
        • 最新的beta版本:3.0.0-beta.24
        • 最新的alpha版本:3.0.0-alpha.13

        可以通過 npm install semver 來檢查一個包的命名是否符合 semver 規則。有關 semver 具體的說明可以看這里

        dependencies & devDependencies

        dependenciesdevDependencies大家應該都不陌生,通過 npm install xx --save 安裝的包會寫入 dependencies 中,通過 npm install xx --save-dev 安裝的包會寫入 devDependencies。

        dependencies 中的包是生產環境的依賴,屬于線上代碼的一部分,比如 vue、axios、veui 等。devDependencies 中的包是開發環境的依賴,只是在本地開發的時候需要依賴這里的包,比如 vue-loader、eslint等。

        我們平時用的 npm install 命令既會安裝 dependencies 中的包,也會安裝 devDependencies 中的包。如果只想安裝 dependencies 中包,可以使用 npm install --production 或者將 NODE_ENV 環境變量設置為 production,通常在生成環境我們會這么用。

        需要注意的是,一個模塊會不會被打包取決于我們在項目中是否引入了該模塊,跟該模塊放在 dependencies 中還是 devDependencies 并沒有關系。

        對于我們的項目來說,把用到的包寫在 dependencies 或者 devDependencies 并沒有什么區別。但要是做為一個包發到 npm 上時,寫在 devDependencies 中的依賴不會被下載。

        peerDependencies & bundledDependencies & optionalDependencies

        這三個屬性在平時我們的項目開發中都用不到。不同于 dependencies & devDependencies面向的是包的使用者,peerDependencies & optionalDependencies & bundledDependencies這三個屬性是面向包的發布者。

        peerDependencies

        我們在一些 node_modules 包的 package.json 中可以看到 peerDependencies,它用來表明如果你想要使用此插件,此插件要求宿主環境所安裝的包。比如項目中用到的 veui1.0.0-alpha.24 版本中:

        "peerDependencies": {
            "vue": "^2.5.16"
         }

        這表明如果你想要使用 veui1.0.0-alpha.24 版本,所要求的 vue 版本需要滿足 >=2.5.16<3.0.0。

        npm3.x 以上版本中,如果安裝結束后宿主環境沒有滿足 peerDependencies 中的要求,會在控制臺打印出警告信息。

        bundledDependencies

        當我們想在本地保留一個 npm 完整的包或者想生成一個壓縮文件來獲取 npm 包的時候,會用到 bundledDependencies。本地使用 npm pack 打包時會將 bundledDependencies 中依賴的包一同打包,當 npm install 時相應的包會同時被安裝。需要注意的是,bundledDependencies 中的包不應該包含具體的版本信息,具體的版本信息需要在 dependencies 中指定。

        例如一個 package.json 文件如下:

        {
          "name": "awesome-web-framework",
          "version": "1.0.0",
          "bundledDependencies": [
            "renderized", 
            "super-streams"
          ]
        }

        當我們執行 npm pack 后會生成 awesome-web-framework-1.0.0.tgz 文件。該文件中包含 renderizedsuper-streams 這兩個依賴,當執行 npm install awesome-web-framework-1.0.0.tgz 下載包時,這兩個依賴會被安裝。

        當我們使用 npm publish 來發布包的話,這個屬性不會起作用。

        optionalDependencies

        從名字上就可以看出,這是可選依賴。如果有包寫在 optionalDependencies 中,即使 npm 找不到或者安裝失敗了也不會影響安裝過程。需要注意的是, optionalDependencies 中的配置會覆蓋 dependencies 中的配置,所以不要將同一個包同時放在這兩個里面。

        如果使用了 optionalDependencies,一定記得要在項目中做好異常處理,獲取不到的情況下應該怎么辦。

        scripts

        定義在 scripts 中的命令,我們通過 npm run <command> 就可以執行。npm run <command>npm run-script <command> 的簡寫。如果不加 command,則會列出當前目錄下可執行的所有腳本。

        test、start、restart、stop 這幾個命令執行時可以不加 run,直接 npm test、npm start、npm restart、npm stop 調用即可。

        env 是一個內置的命令,可以通過 npm run env 可以獲取到腳本運行時的所有環境變量。自定義的 env 命令會覆蓋內置的 env 命令。

        之前開發中遇到一種情況,比如我們想本地通過 http-server 啟動一個服務器,如果事先沒有全局安裝過 http-server 包,只是安裝在對應項目的 node_modules 中。在命令行中輸入 http-server 會報 command not found,但是如果我們在 scripts 中增加如下一條命令就可以執行成功。

        scripts: {
          "server": "http-server",
          "eslint": "eslint --ext .js"
        }

        為什么同樣的命令寫在 scripts 中就可以成功,但是在命令行中執行就不行呢?這是因為 npm run 命令會將 node_modules/.bin/ 加入到 shell 的環境變量 PATH 中,這樣即使局部安裝的包也可以直接執行而不用加 node_modules/.bin/ 前綴。當執行結束后,再將其刪除。

        是不是還是沒明白,下面我們來具體分析一下。

        首先要明確什么是環境變量。環境變量就是系統在執行一個程序,但是沒有明確表明該程序所在的完整路徑時,需要去哪里尋找該程序。

        對于局部安裝的包,拿 eslint 來說,npm 會在本地項目 ./node_modules/.bin 目錄下創建一個指向 ./node_moudles/eslint/bin/eslint.js 名為 eslint 的軟鏈接,即執行 ./node_modules/.bin/eslint 實際上是執行 ./node_moudles/eslint/bin/eslint.js。而當我們執行 npm run eslint 的時候,node_modules/.bin/ 會被加入到環境變量 PATH 中,實際上執行的是 ./node_modules/.bin/eslint,這樣就串起來了。

        理論說完之后,我們來實際驗證一下。

        首先看一下系統的環境變量。直接執行 env 即可。

        然后在當前項目目錄下通過npm run env查看腳本運行時的環境變量。

        通過對比可以發現,運行時的 PATH 多了兩個環境變量。即 npm 指令的路徑和項目 /node_modules/.bin 的路徑。

        以上就是 package.json 中常用 & 重要的幾個屬性,接下來我們來看一看 package-lock.json。

        package-lock.json

        對于 npm,package.json 文件可以看成它的輸入,node_modules 可以做為它的輸出。在理想情況下,npm 應該是一個純函數,無論何時執行相同的 package.json 文件都應該產生完全相同的 node_modules 樹。在一些情況下,這確實可以做到。但是在大多情況下,都實現不了。主要有以下幾個原因:

        • 使用者的 npm 版本有可能不同,不同的 npm 版本有著不同的安裝算法
        • 自上次安裝之后,有些符合 semver-range 的包已經有新的版本發布。這樣再有別人安裝的時候,會安裝符合要求的最新版本。比如引入 vue 包:vue:^2.6.1。A小伙伴下載的時候是 2.6.1,過一陣有另一個小伙伴B入職在安裝包的時候,vue 已經升級到 2.6.2,這樣 npm 就會下載 2.6.2 的包安裝在他的本地
        • 針對第二點,一個解決辦法是固定自己引入的包的版本,但是通常我們不會這么做。即使這樣做了,也只能保證自己引入的包版本固定,也無法保證包的依賴的升級。比如 vue 其中的一個依賴 lodash,lodash:^4.17.4,A下載的是 4.17.4, B下載的時候有可能已經升級到了 4.17.21

        為了解決上述問題,npm5.x 開始增加了 package-lock.json 文件。每當 npm install 執行的時候,npm 都會產生或者更新 package-lock.json 文件。package-lock.json 文件的作用就是鎖定當前的依賴安裝結構,與 node_modules 中下所有包的樹狀結構一一對應。

        有了這個 package-lock.json 文件,就能保證團隊每個人安裝的包版本都是相同的,不會出現有些包升級造成我這好使別人那不好使的兼容性問題。

        下面是 lesspackage-lock.json 文件結構:

        "less": {
            "version": "3.13.1",
            "resolved": "https://registry.npmjs.org/less/-/less-3.13.1.tgz",
            "integrity": "sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw==",
            "dev": true,
            "requires": {
              "copy-anything": "^2.0.1",
              "errno": "^0.1.1",
              "graceful-fs": "^4.1.2",
              "image-size": "~0.5.0",
              "make-dir": "^2.1.0",
              "mime": "^1.4.1",
              "native-request": "^1.0.5",
              "source-map": "~0.6.0",
              "tslib": "^1.10.0"
            },
            dependencies: {
                "copy-anything": {
                  "version": "2.0.3",
                  "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.3.tgz",
                  "integrity": "sha512-GK6QUtisv4fNS+XcI7shX0Gx9ORg7QqIznyfho79JTnX1XhLiyZHfftvGiziqzRiEi/Bjhgpi+D2o7HxJFPnDQ==",
                  "dev": true,
                  "requires": {
                    "is-what": "^3.12.0"
                  }
                  }
            }
         }
        • version: 包的版本信息
        • resoloved: 包的安裝源
        • integrity:一個 hash 值,用來校驗包的完整性
        • dev:布爾值,如果為 true,表明此包如果不是頂層模塊的一個開發依賴(寫在 devDependencies 中),就是一個傳遞依賴(如上面 less 中的 copy-anything)。
        • requires: 對應子依賴的依賴,與依賴包的 package.jsondependencies 的依賴項相同
        • dependencies:結構與外層結構相同,存在于包自己的 node_modules 中的依賴(不是所有的包都有,當子依賴的依賴版本與根目錄的 node_modules 中的依賴沖突時,才會有)

        通過分析上面的 package-lock.json 文件,也許會有一個問題。為什么有的包可以被安裝在根目錄的 node_modules 中,有的包卻只能安裝在自己包下面的 node_modules 中?這就涉及到 npm 的安裝機制。

        npm從 3.x 開始,采用了扁平化的方式來安裝 node_modules。在安裝時,npm 會遍歷整個依賴樹,不管是項目的直接依賴還是子依賴的依賴,都會優先安裝在根目錄的 node_modules 中。遇到相同名稱的包,如果發現根目錄的 node_modules 中存在但是不符合 semver-range,會在子依賴的 node_modules 中安裝符合條件的包。

        具體的安裝算法如下:

        • 從磁盤加載 node_modules
        • 克隆 node_modules
        • 獲取 package.json 文件和分類完畢的元數據信息并把元數據信息插入到克隆樹中
        • 遍歷克隆樹,檢測是否有丟失的依賴。如果有,把他們添加到克隆樹中,依賴會盡可能的添加到最高層
        • 比較原始樹和克隆樹,列出將原始樹轉換為克隆樹所要采取的具體步驟
        • 執行,包括 install, update, remove and move

        npm 官網的例子舉例,假設 package{dep} 結構代表包和包的依賴,現有如下結構:A{B,C}, B{C}, C{D},按照上述算法執行完畢后,生成的 node_modules 結構如下:

        A
        +-- B
        +-- C
        +-- D

        對于 B,C 被安裝在頂層很好理解,因為是 A 的直接依賴。但是 B 又依賴 C,安裝 C 的時候發現頂層已經有 C 了,所以不會在 B 自己的 node_modules 中再次安裝。C 又依賴 D,安裝 D 的時候發現根目錄并沒有 D,所以會把 D 提升到頂層。

        換成 A{B,C}, B{C,D@1}, C{D@2} 這樣的依賴關系后,產生的結構如下:

        A
        +-- B
        +-- C
           +-- D@2
        +-- D@1

        B 又依賴了 D@1,安裝時發現根目錄的 node_modules 沒有,所以會把 D@1 安裝在頂層。C 依賴了 D@2,安裝 D@2 時,因為 npm 不允許同層存在兩個名字相同的包,這樣就與跟目錄 node_modulesD@1 沖突,所以會把 D@2 安裝在 C 自己的 node_modules 中。

        模塊的安裝順序決定了當有相同的依賴時,哪個版本的包會被安裝在頂層。首先項目中主動引入的包肯定會被安裝在頂層,然后會按照包名稱排序(a-z)進行依次安裝,跟包在 package.json 中寫入的順序無關。因此,如果上述將 B{C,D@1} 換成 E{C,D@1},那么 D@2 將會被安裝在頂層。

        有一種情況,當我們項目中所引用的包版本較低,比如 A{B@1,C},而 C 所需要的是 C{B@2} 版本,現在的結構應該如下:

        A
        +-- B@1
        +-- C
           +-- B@2

        有一天我們將項目中的 B 升級到 B@2,理想情況下的結構應該如下:

        A
        +-- B@2
        +-- C

        但是現在 package-lock.json 文件的結構卻是這樣的:

        A
        +-- B@2
        +-- C
           +-- B@2

        B@2 不僅存在于根目錄的 node_modules 下,C 下也同樣存在。這時需要我們手動執行 npm dedupe 進行去重操作,執行完成后會發現 C 下面的 B@2 會消失。大家可以在自己的項目中試一試,優化一下 package-lock.json 文件的結構。

        以下是在我的項目中執行 npm dedupe 的結果:

        removed 41 packages, moved 15 packages and audited 1994 packages in 18.538s

        npm5.x 之前,可以手動通過 npm shrinkwrap 生成 npm-shrinkwrap.json 文件,與 package-lock.json 文件的作用相同。當項目中同時存在 npm-shrinkwrap.jsonpackage-lock.json,將以 npm-shrinkwrap.json 為主。

        執行 npm dedupe 去重之后的 node_modules 會瘦身一些,但做為一個有追求的程序員怎么能局限于僅僅瘦身呢,我們要緊跟時代的潮流,對一些過時的東西say no。這時, npm-outdated 命令就派上用場了。

        npm-outdated 命令是用來檢查項目中用到的包版本在當前是否已經過時。如果有過時的包,會在控制臺打印出信息。默認情況下,只會列出項目中頂層依賴的過時信息。如果想要更深層的查看,可以加上 depth 參數,如 npm-outdated --depth=1

        以下是在我的項目中執行 npm-outdated 的部分結果。從結果中可以看到包的當前版本,符合 semver-range 的最高版本以及當前的最新版本等信息。

        Package          Current          Wanted          Latest         Location
        animate.css      3.7.0            3.7.2           4.1.1          xxx
        autoprefixer     9.7.6            9.8.6           10.2.5         xxx
        axios            0.19.2           0.19.2          0.21.1         xxx
        babel-eslint     7.2.3            7.2.3           10.1.0         xxx
        babel-loader     7.1.5            7.1.5           8.2.2          xxx

        有需求的小伙伴可以嘗試把自己項目中用到的已經過時的包升級一下。

        本文只是一些理論基礎,之后會介紹一些 npm 源碼相關的知識。

        參考文章

        1. npm官網
        2. 前端工程化 - 剖析npm的包管理機制
        3. 前端工程化(5):你所需要的npm知識儲備都在這了
        4. semver
        查看原文

        贊 20 收藏 16 評論 0

        袁鈺涵 發布了文章 · 3月7日

        JS 文件互轉、10 個 HTML 文件上傳技巧、Web 用戶體驗設計提升指南、奇怪的知識——位掩碼 | 思否技術周刊

        今日分享提升工作幸福感的知識點,希望大家不要錯過這些好文~

        1、JS 文件 base64、File、Blob、ArrayBuffer 互轉

        二進制互轉

        1. file對象轉base64
        let?reader?=?new?FileReader();
        ?reader.readAsDataURL(file[0])
        ?console.log(reader)
        1. base64 轉成blob 上傳
        function?dataURItoBlob(dataURI)?{??
        ????var?byteString?=?atob(dataURI.split(',')[1]);??
        ????var?mimeString?=?dataURI.split(',')[0].split(':')[1].split(';')[0];??
        ????var?ab?=?new?ArrayBuffer(byteString.length);??
        ????var?ia?=?new?Uint8Array(ab);??
        ????for?(var?i?=?0;?i?<?byteString.length;?i++)?{??
        ????????ia[i]?=?byteString.charCodeAt(i);??
        ????}??
        ????return?new?Blob([ab],?{type:?mimeString});??
        }
        1. blob 轉成ArrayBuffer
        let?blob?=?new?Blob([1,2,3,4])
        let?reader?=?new?FileReader();
        reader.onload?=?function(result)?{
        ????console.log(result);
        }
        reader.readAsArrayBuffer(blob);
        1. buffer 轉成blob
        let?blob?=?new?Blob([buffer])
        1. base64 轉 file
        const?base64ConvertFile?=?function?(urlData,?filename)?{?//?64轉file
        ??if?(typeof?urlData?!=?'string')?{
        ????this.$toast("urlData不是字符串")
        ????return;
        ??}
        ??var?arr?=?urlData.split(',')
        ??var?type?=?arr[0].match(/:(.*?);/)[1]
        ??var?fileExt?=?type.split('/')[1]
        ??var?bstr?=?atob(arr[1])
        ??var?n?=?bstr.length
        ??var?u8arr?=?new?Uint8Array(n)
        ??while?(n--)?{
        ????u8arr[n]?=?bstr.charCodeAt(n);
        ??}
        ??return?new?File([u8arr],?'filename.'?+?fileExt,?{
        ????type:?type
        ??});
        }

        2、10 個 HTML 文件上傳技巧

        上傳文件功能可以說是項目經常出現的需求。從在社交媒體上上傳照片到在求職網站上發布簡歷,文件上傳無處不在。在本文中,我們將討論 HTML文件上傳支持的10種用法,希望對你有用。

        1. 單文件上傳

        我們可以將input 類型指定為file,以在Web應用程序中使用文件上傳功能。

        <input?type="file"?id="file-uploader">

        input filte 提供按鈕上傳一個或多個文件。默認情況下,它使用操作系統的本機文件瀏覽器上傳單個文件。成功上傳后,File API?使得可以使用簡單的 JS 代碼讀取File對象。要讀取File對象,我們需要監聽?change事件。

        首先,通過id獲取文件上傳的實例:

        const?fileUploader?=?document.getElementById('file-uploader');

        然后添加一個change?事件偵聽器,以在上傳完成后讀取文件對象, 我們從event.target.files屬性獲取上傳的文件信息:

        fileUploader.addEventListener('change',?(event)?=>?{
        ??const?files?=?event.target.files;
        ??console.log('files',?files);
        });

        在控制臺中觀察輸出結果,這里關注一下FileList數組和File對象,該對象具有有關上傳文件的所有元數據信息。

        clipboard.png

        如果大家看到這里,有點激動,想手賤一下,可以 CodePen 玩玩,地址:

        https://codepen.io/atapas/pen...

        2. 多文件上傳

        如果我們想上傳多個文件,需要在標簽上添加 multiple 屬性:

        <input?type="file"?id="file-uploader"?multiple?/>

        現在,我們可以上傳多個文件了,以前面事例為基礎,選擇多個文件上傳后,觀察一下控制臺的變化:

        clipboard.png

        如果大家看到這里,有點激動,想手賤一下,可以 CodePen 玩玩,地址:

        https://codepen.io/atapas/pen...

        3.了解文件元數據

        每當我們上傳文件時,File對象都有元數據信息,例如file name,size,last update time,type 等等。這些信息對于進一步的驗證和特殊處理很有用。

        const fileUploader = document.getElementById('file-uploader');
        
        //?聽更?change?件并讀取元數據
        fileUploader.addEventListener('change',?(event)?=>?{
        ??//?獲取文件列表數組
        ??const?files?=?event.target.files;
        ??//?遍歷并獲取元數據
        ??for?(const?file?of?files)?{
        ????const?name?=?file.name;
        ????const?type?=?file.type???file.type:?'NA';
        ????const?size?=?file.size;
        ????const?lastModified?=?file.lastModified;
        ????console.log({?file,?name,?type,?size,?lastModified?});
        ??}
        });

        下面是單個文件上傳的輸出結果:

        clipboard.png

        如果大家看到這里,有點激動,想手賤一下,可以 CodePen 玩玩,地址:

        https://codepen.io/atapas/pen...

        4.了解?accept?屬性

        我們可以使用accept屬性來限制要上載的文件的類型,如果只想上傳的文件格式是 .jpg,.png 時,可以這么做:

        <input?type="file"?id="file-uploader"?accept=".jpg,?.png"?multiple>

        在上面的代碼中,只能選擇后綴是.jpg和.png的文件。

        如果大家看到這里,有點激動,想手賤一下,可以 CodePen 玩玩,地址:

        https://codepen.io/atapas/pen...

        5. 管理文件內容

        成功上傳文件后顯示文件內容,站在用戶的角度上,如果上傳之后,沒有一個預覽的,就很奇怪也不體貼。

        我們可以使用FileReader對象將文件轉換為二進制字符串。然后添加load?事件偵聽器,以在成功上傳文件時獲取二進制字符串。

        //?FileReader?實例
        const?reader?=?new?FileReader();
        fileUploader.addEventListener('change',?(event)?=>?{
        ??const?files?=?event.target.files;
        ??const?file?=?files[0];
        ??reader.readAsDataURL(file);
        ??reader.addEventListener('load',?(event)?=>?{
        ????const?img?=?document.createElement('img');
        ????imageGrid.appendChild(img);
        ????img.src?=?event.target.result;
        ????img.alt?=?file.name;
        ??});
        });

        如果大家看到這里,有點激動,想手賤一下,可以 CodePen 玩玩,地址:

        https://codepen.io/atapas/pen...

        文章后面還有五個技巧分享,分別是:

        6.驗證文件大小

        1. 顯示文件上傳進度
        2. 怎么上傳目錄上傳?
        3. 拖拽上傳
        4. 使用objectURL處理文件

        可以點擊標題鏈接,去原文章瀏覽全部技巧~

        3、前端優秀實踐不完全指南

        本文應該叫,Web 用戶體驗設計提升指南。

        一個 Web 頁面,一個 APP,想讓別人用的爽,也就是所謂的良好的用戶體驗,我覺得他可能包括但不限于:

        • 急速的打開速度
        • 眼前一亮的 UI 設計
        • 酷炫的動畫效果
        • 豐富的個性化設置
        • 便捷的操作b
        • 貼心的細節
        • 關注殘障人士,良好的可訪問性
        • ...

        所謂的用戶體驗設計,其實是一個比較虛的概念,是秉承著以用戶為中心的思想的一種設計手段,以用戶需求為目標而進行的設計。設計過程注重以用戶為中心,用戶體驗的概念從開發的最早期就開始進入整個流程,并貫穿始終。

        良好的用戶體驗設計,是產品每一個環節共同努力的結果。

        除去一些很難一蹴而就的,本文將就頁面展示、交互細節、可訪問性三個方面入手,羅列一些在實際的開發過程中,積攢的一些有益的經驗。通過本文,你將能收獲到:

        1. 了解到一些小細節是如何影響用戶體驗的
        2. 了解到如何在盡量小的開發改動下,提升頁面的用戶體驗
        3. 了解到一些優秀的交互設計細節
        4. 了解基本的無障礙功能及頁面可訪問性的含義
        5. 了解基本的提升頁面可訪問性的方法

        4、13個頂級免費所見即所得文本編輯器工具

        CKEditor

        CKEditor擁有10多年的開發經驗,你可以完全放心此文本編輯器的質量。它支持70多種語言,我認為這是你網站的不錯選擇。它還可以運行在許多不同的瀏覽器上,并能很好地與大多數前端框架,如reat,vue,angular......你可以使用CDN直接嵌入到你的HTML頁面中......。目前它有兩個版本并行運行的CKEditor4和CKEditor5,根據不同的使用目的,你會選擇適合自己的編輯器。

        https://ckeditor.com/

        Trumbowyg

        Trumbowyg是針對HTML5優化的代碼編輯器,它支持大多數流行的瀏覽器,例如IE9 +,Firefox,Chrome等。據我所知,它包含用于文本編輯的所有工具,僅為20Kb,它輕巧,將幫助你的網站更流暢地運行。此外,它還具有其他支持插件來幫助你更好地工作,例如插入表情符號,其他國家/地區的支持語言,添加聲音,插入特殊字符...

        https://alex-d.github.io/Trumbowyg/

        TinyMCE

        TinyMCE 5是一款編輯器,它能讓你靈活地編輯、添加或刪除本程序中的部分內容。除了基本的編輯器,那么我發現它還提供了很多支持,更好的用戶體驗,如添加評論,測試檢查路徑,提供優質的圖標和界面,檢查拼寫的內容...... 然而,這也是它的弱點,因為如果你想使用高級工具,你必須每月支付約25美元。

        https://www.tiny.cloud/features/

        Quill

        Quill是一個開放源代碼編輯器,因此可以將其用于所有類型的商業或非商業網站。它有很多功能,如添加鏈接,圖像,視頻或添加代碼片段的內容…關于Quill,我最喜歡的一點是它的簡單設置和顯示,可以在多設備屏幕上的所有現代的、響應迅速的web瀏覽器上顯示,還有使用它的常見問題的詳細說明。

        https://quilljs.com/

        Trix

        Trix是一個開源的編輯器,可以讓你在Web中輕松地撰寫消息、寫評論、寫帖子......,并被良好編程的平板電腦使用。如果你只需要創建內容所需的功能,那么Trix同樣是不錯的選擇。

        https://trix-editor.org/

        Jodit Editor 3

        Jodit Editor 3是一個用純TypeScript編寫的開源github編輯器,不使用任何其他庫。它允許你以多種方式設置它,如通過npm、使用CDN......。我喜歡它的是,除了詳細的說明,還有一個程序,通過代碼讓我們自由選擇哪些工具附加到Jodit Editor。

        https://xdsoft.net/jodit/

        Summernote

        Summernote是GitHub上的開源編輯器,獲得了超過9K星。它是通過Bootstrap框架設計的,具有在你的網站上創建內容所需的所有功能。你只需要下載它的源文件css,js,再加上Bootstrap框架(也支持3、4兩個版本)就已經可以為你的網站服務了。

        https://summernote.org/

        Editor.js

        Editor.js是一個開源的塊狀編輯器,它不會像普通的編輯器那樣使用標簽HTML,將內容以JSON的形式輸出,使其更容易管理。它還支持通過使用API的插件,多虧了這一點,應該任何功能 任何開發者都可以為這個程序貢獻更多有趣和有用的插件。

        https://editorjs.io/

        MediumEditor

        MediumEditor是Medium的內置的開放源代碼編輯器,用于人們博客。它僅包含編輯器所需的基本實用程序,因此僅約28kB,這將有助于你的網站得到優化。同時如果我們想要添加其他功能,為了優化編輯,MediumEditor還提供了額外的外部實用工具,定期更新。

        https://yabwe.github.io/medium-editor/

        Wysihtml

        Wysihtml是一個由Voog團隊構建的開源編輯器。它功能齊全,可以幫助你輕松編輯文本,并且支持大多數現代屏幕瀏覽器的設備圖像。有很多工具我很喜歡它是自動轉換不合適的HTML標簽率,自動分析內容時從Word, PDF,顯示內容為HTML…

        http://wysihtml.com/

        ContentTools

        ContentTools是內置的開源編輯器,可幫助你輕松地一種方式編輯HTML內容。它提供了用于編輯內容的各種實用程序,你還可以輕松地將Message Institute和其他實用程序添加到程序中(請參閱脫機API部分)。我還發現了如何設置,添加或刪除程序中的函數的文章…都是非常細致的。

        https://getcontenttools.com/demo

        Froala

        Froala是一個編輯器,可以很容易地為網站設置,并允許你根據預期用途打開廣泛的功能。由于它是用純JavaScript編寫的,因此你可以將其用于當今的大多數現代前端框架。它還提供了許多有用的工具,以及編輯圖像,添加或編輯視頻,添加圖標,管理面板等。但是,如果你要使用該工具用于商業目的,則必須購買許可證。

        https://froala.com/wysiwyg-editor/tour/

        Redactor

        Redactor是一款功能齊全的編輯器,具有精美而簡單的設計。超過9年的發展,包括很多支持插件,我想這是一個很好的產品。另外它對程序員在使用程序的過程中遇到的每一個常見問題都有極其詳細的實例。但是,它也有一個缺點,當你將其用于商業目的時必須購買許可證。

        https://imperavi.com/redactor/

        5、奇怪的知識——位掩碼

        假設我們有一個權限系統,它通過 JSON 的方式記錄了某個用戶的權限開通情況(姑且假設權限集是 CURD):

        const?permission?=?{
        ??create:?false,
        ??update:?false,
        ??read:?true,
        ??delete:?false,
        }

        如果我們把 false 寫成 0,true 寫成 1,那么這個 permisson 對象可以簡寫為?0b0010。

        const?permission?=?{
        ??create:?false,
        ??update:?false,
        ??read:?true,
        ??delete:?false,
        }
        //?從左往右,依次為?create,?update,?read,?delete?所對應的值
        const?permissionBinary?=?0b0010

        對于 JSON 對象的權限集,如果我們要查看或者修改該用戶的某些權限,只需要通過形如 permission.craete 的普通對象操作即可。那么如果對于二進制形式的權限集,我們又應該如何進行查看或者修改的操作呢?接下來我們就開始使用奇怪的知識——位掩碼來進行了。

        位掩碼

        首先進行名詞解釋,什么是”位掩碼“。

        位掩碼(BitMask),是”位(Bit)“和”掩碼(Mask)“的組合詞?!蔽弧爸复M制數據當中的二進制位,而”掩碼“指的是一串用于與目標數據進行按位操作的二進制數字。組合起來,就是”用一串二進制數字(掩碼)去操作另一串二進制數字“的意思。

        明白了位掩碼的作用以后,我們就可以通過它來對權限集二進制數進行操作了。

        1、查詢用戶是否擁有某個權限

        已知用戶權限集二進制數為 permissionBinary = 0b0010。如果我想知道該用戶是否存在?update?這個權限,可以先給定一個位掩碼?mask = 0b1。

        image.png

        由于 update 位于右數第三項,所以只需要把位掩碼向左移動兩位,剩余位置補0。最后和權限集二進制數進行按位與運算即可得到結果。

        image.png

        最后算出來的 result 為?0b0000,使用 Boolean()?函數處理之即可得到 false 的結果,也就是說該用戶的 update 權限為 false。

        //?從左往右,依次為?create,?update,?read,?delete?所對應的值
        const?permissionBinary?=?0b0010
        //?由于?update?位于右數第三位,因此只需要讓掩碼向左移動2位即可
        const?mask?=?0b1?<<?2
        const?result?=?permissionBinary?&?mask
        Boolean(result)?//?false

        2、修改用戶的某個權限

        當我們明白了如何用位掩碼來查詢權限后,要修改對應的權限也就手到擒來了,無非就是換一種位運算。假設還是?update?權限,如果我想把它修改成?true,我們可以這么干:

        image.png

        只需要把按位與改為按位異或即可,代碼如下:

        //?從左往右,依次為?create,?update,?read,?delete?所對應的值
        const?permissionBinary?=?0b0010
        //?由于?update?位于右數第三位,因此只需要讓掩碼向左移動2位即可
        const?mask?=?0b1?<<?2
        const?result?=?permissionBinary?^?mask
        parseInt(result).toString(2)?//?0b0110

        經過上面的內容,相信你已經基本掌握了位掩碼的知識,同時你肯定還有很多問號,比如說這么復雜又不好閱讀的代碼,真的有意義嗎?

        臟數據記錄

        前文例子中的權限系統僅有區區4個數據的處理,位掩碼技術顯得復雜又小題大做。那么有沒有什么場景是真的適合使用位掩碼的呢?臟數據記錄就是其中一個。

        假設我們存在著一份原始數據,其值如下:

        let?A?=?'a'
        let?B?=?'b'
        let?C?=?'c'
        let?D?=?'d'

        給定一個二進制數,從左往右分別對應著 A/B/C/D 的狀態:

        let?O?=?0b0000?//?十進制?0

        則數據一旦發生了修改,都可以用對應的比特位來表示

        //?當且僅當?A?發生了修改
        O?=?0b1000?//?十進制?8
        //?當且僅當?B?發生了修改
        O?=?0b0100?//?十進制?4
        //?當且僅當?C?發生了修改
        O?=?0b0010?//?十進制?2
        //?當且僅當?D?發生了修改
        O?=?0b0001?//?十進制?1

        同理,當多個數據發生了修改時,則可以同時表示

        //?當?A?和?B?發生了修改
        O?=?0b1100?//?十進制?12
        //?當?A/B/C?都發生了修改
        O?=?0b1110?//?十進制?14

        通過這個思路,應用排列組合的思想,可以很快知道只需要僅僅 4 個比特位,就可以表達 16 種數據變化的情況。由于二進制和十進制可以相互轉化,因此只需要區區 16 個十進制數,就可以完整地表達 A/B/C/D 這四個數據的變化情況,也就是臟數據追蹤。舉個例子,給定一個臟數據記錄 14,二進制轉換為?0b1110,因此表示 A/B/C 的數據被修改了。

        Svelte 這個框架,就是通過這個思路來實現響應式的:

        if?(?A?數據變了?)?{
        ??更新A對應的DOM節點
        }
        if?(?B?數據變了?)?{
        ??更新B對應的DOM節點
        }
        /**?轉化成偽代碼?**/
        if?(?dirty?&?8?)?{?//?8?===?0b1000
        ??更新A對應的DOM節點
        }
        if?(?dirty?&?4?)?{?//?4?===?0b0100
        ??更新B對應的DOM節點
        }

        老鼠喝毒藥

        除了用來做臟數據記錄以外,位掩碼也能夠用來處理經典的”老鼠喝毒藥“的問題。

        有 1000 瓶水,其中有一瓶有毒,小白鼠只要嘗一點帶毒的水24小時后就會死亡,問至少要多少只小白鼠才能在24小時內鑒別出哪瓶水有毒?

        我們簡化一下問題,假設只有 8 瓶水,其編號用二進制表示:

        image.png

        接著按照圖示的方式對水瓶的水進行混合,得到樣品 A/B/C/D,取4只老鼠編號為 a/b/c/d 分別喝下對應的水,得到如下的表格:

        image.png

        在 24 小時候,統計老鼠的死亡情況,匯總后可以得到表格和結果:

        image.png

        答案呼之欲出,由于 8 瓶水可以兌出 4 份樣品,因此只需要 4 只老鼠即可在 24 小時后確定到底哪一瓶水是有毒的?;氐筋}目,如果是 1000 瓶水,只需要知道第 1000 號的二進制數?0b1111101000即可。該二進制數一共有 10 個比特位,意味著 1000 瓶水可以兌出 10 份樣品,也就是說只需要 10 只老鼠,就可以完成測試任務。

        尾聲

        關于位掩碼技術的探索就到這里。相信在認真讀完這篇文章以后,大家心里已經建立起對位掩碼技術的概念。這是一種非常特別的問題解決思路,也許在未來的某一天你真的會用上它。


        image.png

        查看原文

        贊 23 收藏 17 評論 0

        袁鈺涵 贊了文章 · 3月7日

        前端優秀實踐不完全指南

        本文其實應該叫,Web 用戶體驗設計提升指南。

        一個 Web 頁面,一個 APP,想讓別人用的爽,也就是所謂的良好的用戶體驗,我覺得他可能包括但不限于:

        • 急速的打開速度
        • 眼前一亮的 UI 設計
        • 酷炫的動畫效果
        • 豐富的個性化設置
        • 便捷的操作b
        • 貼心的細節
        • 關注殘障人士,良好的可訪問性
        • ...

        所謂的用戶體驗設計,其實是一個比較虛的概念,是秉承著以用戶為中心的思想的一種設計手段,以用戶需求為目標而進行的設計。設計過程注重以用戶為中心,用戶體驗的概念從開發的最早期就開始進入整個流程,并貫穿始終。

        良好的用戶體驗設計,是產品每一個環節共同努力的結果。

        除去一些很難一蹴而就的,本文將就頁面展示、交互細節、可訪問性三個方面入手,羅列一些在實際的開發過程中,積攢的一些有益的經驗。通過本文,你將能收獲到:

        1. 了解到一些小細節是如何影響用戶體驗的
        2. 了解到如何在盡量小的開發改動下,提升頁面的用戶體驗
        3. 了解到一些優秀的交互設計細節
        4. 了解基本的無障礙功能及頁面可訪問性的含義
        5. 了解基本的提升頁面可訪問性的方法

        頁面展示

        就整個頁面的展示,頁面內容的呈現而言,有一些小細節是需要我們注意的。

        整體布局

        先來看看一些布局相關的問題。

        對于大部分 PC 端的項目,我們首先需要考慮的肯定是最外層的一層包裹。假設就是 .g-app-wrapper。

        <div class="g-app-wrapper">
            <!-- 內部內容 -->
        </div>

        首先,對于 .g-app-wrapper,有幾點,是我們在項目開發前必須弄清楚的:

        1. 項目是全屏布局還是定寬布局?
        2. 對于全屏布局,需要適配的最小的寬度是多少?

        對于定寬布局,就比較方便了,假設定寬為 1200px,那么:

        .g-app-wrapper {
            width: 1200px;
            margin: 0 auto;
        }

        利用 margin: 0 auto 實現布局的水平居中。在屏幕寬度大于 1200px 時,兩側留白,當然屏幕寬度小于 1200px 時,則出現滾動條,保證內部內容不亂。

        layout1

        對于現代布局,更多的是全屏布局。其實現在也更提倡這種布局,即使用可隨用戶設備的尺寸和能力而變化的自適應布局。

        通常而言是左右兩欄,左側定寬,右側自適應剩余寬度,當然,會有一個最小的寬度。那么,它的布局應該是這樣:

        <div class="g-app-wrapper">
            <div class="g-sidebar"></div>
            <div class="g-main"></div>
        </div>
        .g-app-wrapper {
            display: flex;
            min-width: 1200px;
        }
        .g-sidebar {
            flex-basis: 250px;
            margin-right: 10px;
        }
        .g-main {
            flex-grow: 1;
        }

        layout2

        利用了 flex 布局下的 flex-grow: 1,讓 .main 進行伸縮,占滿剩余空間,利用 min-width 保證了整個容器的最小寬度。

        當然,這是最基本的自適應布局。對于現代布局,我們應該盡可能的考慮更多的場景。做到:

        image

        底部 footer

        下面一種情形也是非常常見的一個情景。

        頁面存在一個 footer 頁腳部分,如果整個頁面的內容高度小于視窗的高度,則 footer 固定在視窗底部,如果整個頁面的內容高度大于視窗的高度,則 footer 正常流排布(也就是需要滾動到底部才能看到 footer)。

        看看效果:

        margintopauto

        嗯,這個需求如果能夠使用 flex 的話,使用 justify-content: space-between 可以很好的解決,同理使用 margin-top: auto 也非常容易完成:

        <div class="g-container">
            <div class="g-real-box">
                ...
            </div>
            <div class="g-footer"></div>
        </div>
        .g-container {
            height: 100vh;
            display: flex;
            flex-direction: column;
        }
        
        .g-footer {
            margin-top: auto;
            flex-shrink: 0;
            height: 30px;
            background: deeppink;
        }

        Codepen Demo -- sticky footer by flex margin auto

        當然,實現它的方法有很多,這里僅給出一種推薦的解法。

        處理動態內容 - 文本超長

        對于所有接收后端接口字段的文本展示類的界面。都需要考慮全面(防御性編程:所有的外部數據都是不可信的),正常情況如下,是沒有問題的。

        image

        但是我們是否考慮到了文本會超長?超長了會折行還是換行?

        image

        對于單行文本,使用單行省略:

        {
            width: 200px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        image

        當然,目前對于多行文本的超長省略,兼容性也已經非常好了:

        {
            width: 200px;
            overflow : hidden;
            text-overflow: ellipsis;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
        }

        image

        處理動態內容 - 保護邊界

        對于一些動態內容,我們經常使用 min/max-widthmin/max-height 對容器的高寬限度進行合理的控制。

        在使用它們的時候,也有一些細節需要考慮到。

        譬如經常會使用 min-width 控制按鈕的最小寬度:

        .btn {
            ...
            min-width: 120px;
        }

        image

        當內容比較少的時候是沒問題的,但是當內容比較長,就容易出現問題。使用了 min-width 卻沒考慮到按鈕的過長的情況:

        image

        這里就需要配合 padding 一起:

        .btn {
            ...
            min-width: 88px;
            padding: 0 16px
        }

        借用Min and Max Width/Height in CSS中一張非常好的圖,作為釋義:

        min-width-2

        0 內容展示

        這個也是一個常常被忽略的地方。

        頁面經常會有列表搜索,列表展示。那么,既然存在有數據的正常情況,當然也會存在搜索不到結果或者列表無內容可展示的情形。

        對于這種情況,一定要注意 0 結果頁面的設計,同時也要知道,這也是引導用戶的好地方。對于 0 結果頁面,分清楚:

        • 數據為空:其中又可能包括了用戶無權限、搜索無結果、篩選無結果、頁面無數據
        • 異常狀態:其中又可能包括了網絡異常、服務器異常、加載失敗等待

        不同的情況可能對應不同的 0 結果頁面,附帶不同的操作引導。

        譬如網絡異常:

        image

        或者確實是 0 結果:

        image

        關于 0 結果頁面設計,可以詳細看看這篇文章:如何設計產品的空白頁面?

        小小總結一下,上述比較長的篇幅一直都在闡述一個道理,開發時,不能僅僅關注正?,F象,要多考慮各種異常情況,思考全面。做好各種可能情況的處理。

        圖片相關

        圖片在我們的業務中應該是非常的常見了。有一些小細節是需要注意的。

        給圖片同時設置高寬

        有的時候和產品、設計會商定,只能使用固定尺寸大小的圖片,我們的布局可能是這樣:

        image

        對應的布局:

        <ul class="g-container">
            <li>
                <img data-original="http://placehold.it/150x100">
                <p>圖片描述</p>
            </li>
        </ul>
        ul li img {
            width: 150px;
        }

        當然,萬一假設后端接口出現一張非正常大小的圖片,上述不加保護的布局就會出問題:

        image

        所以對于圖片,我們總是建議同時寫上高和寬,避免因為圖片尺寸錯誤帶來的布局問題:

        ul li img {
            width: 150px;
            height: 100px;
        }

        同時,給 <img> 標簽同時寫上高寬,可以在圖片未加載之前提前占住位置,避免圖片從未加載狀態到渲染完成狀態高寬變化引起的重排問題。

        object-fit

        當然,限制高寬也會出現問題,譬如圖片被拉伸了,非常的難看:

        image

        這個時候,我們可以借助 object-fit,它能夠指定可替換元素的內容(也就是圖片)該如何適應它的父容器的高寬。

        ul li img {
            width: 150px;
            height: 100px;
            object-fit: cover;
        }

        利用 object-fit: cover,使圖片內容在保持其寬高比的同時填充元素的整個內容框。

        image

        object-fit 還有一個配套屬性 object-position,它可以控制圖片在其內容框中的位置。(類似于 background-position),m默認是 object-position: 50% 50%,如果你不希望圖片居中展示,可以使用它去改變圖片實際展示的 position 。

        ul li img {
            width: 150px;
            height: 100px;
            object-fit: cover;
            object-position: 50% 100%;
        }

        image

        像是這樣,object-position: 100% 50% 指明從底部開始展示圖片。這里有一個很好的 Demo 可以幫助你理解 object-position。

        CodePen Demo -- Object position

        考慮屏幕 dpr -- 響應式圖片

        正常情況下,圖片的展示應該沒有什么問題了。但是對于有圖片可展示的情況下,我們還可以做的更好。

        在移動端或者一些高清的 PC 屏幕(蘋果的 MAC Book),屏幕的 dpr 可能大于 1。這種時候,我們可能還需要考慮利用多倍圖去適配不同 dpr 的屏幕。

        正好,<img> 標簽是有提供相應的屬性 srcset 讓我們進行操作的。

        <img data-original='photo@1x.png'
           srcset='photo@1x.png 1x,
                   photo@2x.png 2x,
                   photo@3x.png 3x' 
        />

        當然,這是比較舊的寫法,srcset 新增了新的 w 寬度描述符,需要配合 sizes 一起使用,所以更好的寫法是:

        <img 
                src = "photo.png" 
                sizes = “(min-width: 600px) 600px, 300px" 
                srcset = “photo@1x.png 300w,
                               photo@2x.png 600w,
                               photo@3x.png 1200w,
        >

        利用 srcset,我們可以給不同 dpr 的屏幕,提供最適合的圖片。

        上述出現了一些概念,dpr,圖片的 srcset ,sizes 屬性,,不太了解的可以移步 前端基礎知識概述

        圖片丟失

        好了,當圖片鏈接沒問題時,已經處理好了。接下來還需要考慮,當圖片鏈接掛了,應該如何處理。

        處理的方式有很多種。最好的處理方式,是我最近在張鑫旭老師的這篇文章中 -- 圖片加載失敗后CSS樣式處理最佳實踐 看到的。這里簡單講下:

        1. 利用圖片加載失敗,觸發 <img> 元素的 onerror 事件,給加載失敗的 <img> 元素新增一個樣式類
        2. 利用新增的樣式類,配合 <img> 元素的偽元素,展示默認兜底圖的同時,還能一起展示 <img> 元素的 alt 信息
        <img data-original="test.png" alt="圖片描述" onerror="this.classList.add('error');">
        img.error {
            position: relative;
            display: inline-block;
        }
        
        img.error::before {
            content: "";
            /** 定位代碼 **/
            background: url(error-default.png);
        }
        
        img.error::after {
            content: attr(alt);
            /** 定位代碼 **/
        }

        我們利用偽元素 before ,加載默認錯誤兜底圖,利用偽元素 after,展示圖片的 alt 信息:

        image

        OK,到此,完整的對圖片的處理就算完成了,完整的 Demo 你可以戳這里看看:

        CodePen Demo -- 圖片處理

        交互設計優化

        接下來一個大環節是關于一些交互的細節。對于交互設計,一些比較通用的準則:

        • Don’t make me think
        • 符合用戶的習慣與預期
        • 操作便利
        • 做適當的提醒
        • 不強迫用戶

        過渡與動畫

        在我們的交互過程中,適當的增加過渡與動畫,能夠很好的讓用戶感知到頁面的變化。

        譬如我們頁面上隨處可見 loading 效果,其實就是這樣一種作用,讓用戶感知頁面正在加載,或者正在處理某些事務。

        滾動優化

        滾動也是操作網頁中非常重要的一環??纯从心男┛梢詢灮狞c:

        滾動平滑:使用 scroll-behavior: smooth 讓滾動絲滑

        使用 scroll-behavior: smooth,可以讓滾動框實現平穩的滾動,而不是突兀的跳動??纯葱Ч?,假設如下結構:

        <div class="g-container">
          <nav>
            <a href="#1">1</a>
            <a href="#2">2</a>
            <a href="#3">3</a>
          </nav>
          <div class="scrolling-box">
            <section id="1">First section</section>
            <section id="2">Second section</section>
            <section id="3">Third section</section>
          </div>
        </div>

        不使用 scroll-behavior: smooth,是突兀的跳動切換:

        scrol

        給可滾動容器添加 scroll-behavior: smooth,實現平滑滾動:

        {
            scroll-behavior: smooth;
        }

        scroll2

        使用 scroll-snap-type 優化滾動效果

        sroll-snap-type 可能算得上是新的滾動規范里面最核心的一個屬性樣式。

        scroll-snap-type:屬性定義在滾動容器中的一個臨時點(snap point)如何被嚴格的執行。

        光看定義有點難理解,簡單而言,這個屬性規定了一個容器是否對內部滾動動作進行捕捉,并且規定了如何去處理滾動結束狀態。讓滾動操作結束后,元素停止在適合的位置。

        看個簡單示例:

        當然,scroll-snap-type 用法非常多,可控制優化的點很多,限于篇幅無法一一展開,具體更詳細的用法可以看看我的另外一篇文章 -- 使用 sroll-snap-type 優化滾動

        控制滾動層級,避免頁面大量重排

        這個優化可能稍微有一點難理解。需要了解 CSS 渲染優化的相關知識。

        先說結論,控制滾動層級的意思是盡量讓需要進行 CSS 動畫(可以是元素的動畫,也可以是容器的滾動)的元素的 z-index 保持在頁面最上方,避免瀏覽器創建不必要的圖形層(GraphicsLayer),能夠很好的提升渲染性能。

        這一點怎么理解呢,一個元素觸發創建一個 Graphics Layer 層的其中一個因素是:

        • 元素有一個 z-index 較低且包含一個復合層的兄弟元素

        根據上述這點,我們對滾動性能進行優化的時候,需要注意兩點:

        1. 通過生成獨立的 GraphicsLayer,利用 GPU 加速,提升滾動的性能
        2. 如果本身滾動沒有性能問題,不需要獨立的 GraphicsLayer,也要注意滾動容器的層級,避免因為層級過高而被其他創建了 GraphicsLayer 的元素合并,被動的生成一個 Graphics Layer ,影響頁面整體的渲染性能

        如果你對這點還有點懵,可以看看這篇文章 -- 你所不知道的 CSS 動畫技巧與細節

        點擊交互優化

        在用戶點擊交互方面,也有一些有意思的小細節。

        優化手勢 -- 不同場景應用不同 cursor

        對于不同的內容,最好給與不同的 cursor 樣式,CSS 原生提供非常多種常用的手勢。

        在不同的場景使用不同的鼠標手勢,符合用戶的習慣與預期,可以很好的提升用戶的交互體驗。

        首先對于按鈕,就至少會有 3 種不同的 cursor,分別是可點擊,不可點擊,等待中:

        {
            cursor: pointer;    // 可點擊
            cursor: not-allowed;    // 不可點擊
            cursor: wait;    // loading
        }

        image

        除此之外,還有一些常見的,對于一些可輸入的 Input 框,使用 cursor: text,對于提示 Tips 類使用 cursor: help,放大縮小圖片 zoom-in、zoom-out 等等:

        image

        一些常用的簡單列一列:

        • 按鈕可點擊: cursor: pointer
        • 按鈕禁止點擊:cursor: not-allowed
        • 等待 Loading 狀態:cursor: wait
        • 輸入框:cursor: text;
        • 圖片查看器可放大可縮?。?code>cursor: zoom-in/ zoom-out
        • 提示:cursor: help;

        當然,實際 cursor 還支持非常多種,可以在 MDN 或者下面這個 CodePen Demo 中查看這里看完整的列表:

        CodePen Demo -- Cursor Demo

        點擊區域優化 -- 偽元素擴大點擊區域

        按鈕是我們網頁設計中十分重要的一環,而按鈕的設計也與用戶體驗息息相關。

        考慮這樣一個場景,在搖晃的車廂上或者是單手操作著屏幕,有的時候一個按鈕,死活也點不到。

        讓用戶更容易的點擊到按鈕無疑能很好的增加用戶體驗及可提升頁面的訪問性,尤其是在移動端,按鈕通常都很小,但是受限于設計稿或者整體 UI 風格,我們不能直接去改變按鈕元素的高寬。

        那么這個時候有什么辦法在不改變按鈕原本大小的情況下去增加他的點擊熱區呢?

        這里,偽元素也是可以代表其宿主元素來響應的鼠標交互事件的。借助偽元素可以輕松幫我們實現,我們可以這樣寫:

        .btn::before{
          content:"";
          position:absolute;
          top:-10px;
          right:-10px;
          bottom:-10px;
          left:-10px;
        }

        當然,在 PC 端下這樣子看起來有點奇怪,但是合理的用在點擊區域較小的移動端則能取到十分好的效果,效果如下:

        608782-20160527112625428-906375003

        在按鈕的偽元素沒有其它用途的時候,這個方法確實是個很好的提升用戶體驗的點。

        快速選擇優化 -- user-select: all

        操作系統或者瀏覽器通常會提供一些快速選取文本的功能,看看下面的示意圖:

        layout3

        快速單擊兩次,可以選中單個單詞,快速單擊三次,可以選中一整行內容。但是如果有的時候我們的核心內容,被分隔符分割,或者潛藏在一整行中的一部分,這個時候選取起來就比較麻煩。

        利用 user-select: all,可以將需要一次選中的內容進行包裹,用戶只需要點擊一次,就可以選中該段信息:

        .g-select-all {
            user-select: all
        }

        給需要一次選中的信息,加上這個樣式后的效果,這個細節作用在一些需要復制粘貼的場景,非常好用:

        layout4

        CodePen -- user-select: all 示例

        選中樣式優化 -- ::selection

        當然,如果你想更進一步,CSS 還有提供一個 ::selection 偽類,可以控制選中的文本的樣式(只能控制color, background, text-shadow),進一步加深效果。

        layout5

        CodePen -- user-select: all && ::selection 控制選中樣式

        添加禁止選擇 -- user-select: none

        有快速選擇,也就會有它的對立面 -- 禁止選擇。

        對于一些可能頻繁操作的按鈕,可能出現如下尷尬的場景:

        • 文本按鈕的快速點擊,觸發了瀏覽器的雙擊快速選擇,導致文本被選中:

        btn-click

        • 翻頁按鈕的快速點擊,觸發了瀏覽器的雙擊快速選擇:

        img-click

        對于這種場景,我們需要把不可被選中元素設置為不可被選中,利用 CSS 可以快速的實現這一點:

        {
            -webkit-user-select: none; /* Safari */
            -ms-user-select: none; /* IE 10 and IE 11 */
            user-select: none; /* Standard syntax */
        }

        這樣,無論點擊的頻率多快,都不會出現尷尬的內容選中:

        btn-click-unselect

        跳轉優化

        現階段,單頁應用(Single Page Application)的應用非常廣泛,Vue 、React 等框架大行其道。但是一些常見的寫法,也容易衍生一些小問題。

        譬如,點擊按鈕、文本進行路由跳轉。譬如,經常會出現這種代碼:

        <template>
            ...
            <button @click="gotoDetail">
                Detail
            </button>
            ...
        <template>
        ...
        gotoDetail() {
            this.$router.push({
              name: 'xxxxx',
            });
        }

        大致邏輯就是給按鈕添加一個事件,點擊之后,跳轉到另外一個路由。當然,本身這個功能是沒有任何問題的,但是沒有考慮到用戶實際使用的場景。

        實際使用的時候,由于是一個頁面跳轉,很多時候,用戶希望能夠保留當前頁面的內容,同時打開一個新的窗口,這個時候,他會嘗試下的鼠標右鍵,選擇在新標簽頁中打開頁面,遺憾的是,上述的寫法是不支持鼠標右鍵打開新頁面的。

        原因在于瀏覽器是通過讀取 <a> 標簽的 href 屬性,來展示類似在新標簽頁中打開頁面這種選項,對于上述的寫法,瀏覽器是無法識別它是一個可以跳轉的鏈接。簡單的示意圖如下:

        image

        所以,對于所有路由跳轉按鈕,建議都使用 <a> 標簽,并且內置 href 屬性,填寫跳轉的路由地址。實際渲染出來的 DOM 可能是需要類似這樣:

        <a href="/xx/detail">Detail</a>

        易用性

        易用性也是交互設計中需要考慮的一個非常重要的環節,能做的有非常多。簡單的羅列一下:

        • 注意界面元素的一致性,降低用戶學習成本
        • 延續用戶日常的使用習慣,而不是重新創造
        • 給下拉框增加一些預設值,降低用戶填寫成本
        • 同類的操作合并在一起,降低用戶的認知成本
        • 任何操作之后都要給出反饋,讓用戶知道操作已經生效

        先探索,后表態

        這一點非常的有意思,什么叫先探索后表態呢?就是我們不要一上來就強迫用戶去做一些事情,譬如登錄。

        想一想一些常用網站的例子:

        • 類似虎牙、Bilibili 等視頻網站,可以先藍光體驗,一定觀看時間后才會要求登錄
        • 電商網站,只有到付款的時候,才需要登錄

        上述易用性先探索,后表態的內容,部分來源于:Learn From What Leading Companies A/B Test,可以好好讀一讀。

        字體優化

        字體的選擇與使用其實是非常有講究的。

        如果網站沒有強制必須使用某些字體。最新的規范建議我們更多的去使用系統默認字體。也就是 CSS Fonts Module Level 4 -- Generic font families 中新增的 font-family: system-ui 關鍵字。

        font-family: system-ui 能夠自動選擇本操作系統下的默認系統字體。

        默認使用特定操作系統的系統字體可以提高性能,因為瀏覽器或者 webview 不必去下載任何字體文件,而是使用已有的字體文件。 font-family: system-ui 字體設置的優勢之處在于它與當前操作系統使用的字體相匹配,對于文本內容而言,它可以得到最恰當的展示。

        舉兩個例子,天貓的字體定義與 Github 的字體定義:

        • 天貓font-family: "PingFang SC",miui,system-ui,-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,sans-serif;
        • Githubfont-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;

        簡單而言,它們總體遵循了這樣一個基本原則:

        1、盡量使用系統默認字體

        使用系統默認字體的主要原因是性能,并且系統字體的優點在于它與當前操作系統使用的相匹配,因此它的文本展示必然也是一個讓人舒適展示效果。

        2、兼顧中西,西文在前,中文在后

        中文或者西文(英文)都要考慮到。由于大部分中文字體也是帶有英文部分的,但是英文部分又不怎么好看,但是英文字體中大多不包含中文。通常會先進行英文字體的聲明,選擇最優的英文字體,這樣不會影響到中文字體的選擇,中文字體聲明則緊隨其次。

        3、兼顧多操作系統

        選擇字體的時候要考慮多操作系統。例如 MAC OS 下的很多中文字體在 Windows 都沒有預裝,為了保證 MAC 用戶的體驗,在定義中文字體的時候,先定義 MAC 用戶的中文字體,再定義 Windows 用戶的中文字體;

        4、兼顧舊操作系統,以字體族系列?serif?和?sans-serif?結尾

        當使用一些非常新的字體時,要考慮向下兼容,兼顧到一些極舊的操作系統,使用字體族系列?serif?和?sans-serif?結尾總歸是不錯的選擇。

        對于上述的一些字體可能會有些懵,譬如 -apple-system, BlinkMacSystemFont,這是因為不同瀏覽器廠商對規范的實現有所不同,對于字體定義更多的相關細節,可以再看看這篇文章 -- Web 字體 font-family 再探秘

        可訪問性(A11Y)

        可訪問性,在我們的網站中,屬于非常重要的一環,但是大部分前端(其實應該是設計、前端、產品)同學都會忽視它。

        我潛伏在一個叫無障礙設計小組的群里,其中包含了很多無障礙設計師以及患有一定程度視覺、聽力、行動障礙的用戶,他們在群里經常會表達出一個觀點,就是國內的大部分 Web 網站及 APP 基本沒有考慮過殘障人士的使用(或者可訪問性做的非常差),非常的令人揪心。

        尤其在我們一些重交互、重邏輯的網站中,我們需要考慮用戶的使用習慣、使用場景,從高可訪問性的角度考慮,譬如假設用戶沒有鼠標,僅僅使用鍵盤,能否順暢的使用我們的網站?

        假設用戶沒有鼠標,這個真不一定是針對殘障人士,很多情況下,用戶拿鼠標的手可能在干其他事情,比如在吃東西,又或者在 TO B 類的業務,如超市收銀、倉庫收貨,很可能用戶拿鼠標的手操作著其他設備(掃碼槍)等等。

        本文不會專門闡述無障礙設計的方方面面,只是從一些我覺得前端工程師需要關注的,并且僅需要花費少量代價就能做好的一些無障礙設計細節。記住,無障礙設計對所有人都更友善。

        色彩對比度

        顏色,也是我們天天需要打交道的屬性。對于大部分視覺正常的用戶,可能對頁面的顏色敏感度還沒那么高。但是對于一小部分色弱、色盲用戶,他們對于網站的顏色會更加敏感,不好的設計會給他們訪問網站帶來極大的不便。

        什么是色彩對比度

        是否曾關心過頁面內容的展示,使用的顏色是否恰當?色弱、色盲用戶能否正??辞鍍热??良好的色彩使用,在任何時候都是有益的,而且不僅僅局限于對于色弱、色盲用戶。在戶外用手機、陽光很強看不清,符合無障礙標準的高清晰度、高對比度文字就更容易閱讀。

        這里就有一個概念 -- 顏色對比度,簡單地說,描述就是兩種顏色在亮度(Brightness)上的差別。運用到我們的頁面上,大多數的情況就是背景色(background-color)與內容顏色(color)的對比差異。

        最權威的互聯網無障礙規范 —— WCAG AA規范規定,所有重要內容的色彩對比度需要達到 4.5:1 或以上(字號大于18號時達到 3:1 或以上),才算擁有較好的可讀性。

        借用一張圖 -- 知乎 -- 助你輕松做好無障礙的15個UI設計工具推薦

        很明顯,上述最后一個例子,文字已經非常的不清晰了,正常用戶都已經很難看得清了。

        檢查色彩對比度的工具

        Chrome 瀏覽器從很早開始,就已經支持檢查元素的色彩對比度了。以我當前正在寫作的頁面為例子,Github Issues 編輯頁面的兩個按鈕:

        image

        審查元素,分別可以看到兩個按鈕的色彩對比度:

        image

        可以看到,綠底白字按鈕的色彩對比度是沒有達到標準的,也被用黃色的嘆號標識了出來。

        除此之外,在審查元素的 Style 界面的取色器,改變顏色,也能直觀的看到當前的色彩對比度:

        image

        焦點響應

        類似百度、谷歌的首頁,進入頁面后會默認讓輸入框獲得焦點:

        image

        并非所有的有輸入框的頁面,都需要進入頁面后進行聚焦,但是焦點能夠讓用戶非常明確的知道,當前自己在哪,需要做些什么。尤其是對于無法操作鼠標的用戶。

        頁面上可以聚焦的元素,稱為可聚焦元素,獲得焦點的元素,則會觸發該元素的 focus 事件,對應的,也就會觸發該元素的 :focus 偽類。

        瀏覽器通常會使用元素的 :focus 偽類,給元素添加一層邊框,告訴用戶,當前的獲焦元素在哪里。

        我們可以通過鍵盤的 Tab 鍵,進行焦點的切換,而獲焦元素則可以通過元素的 :focus 偽類的樣式,告訴用戶當前焦點位置。

        當然,除了 Tab 鍵之外,對于一些多輸入框、選擇框的表單頁面,我們也應該想著如何簡化用戶的操作,譬如用戶按回車鍵時自動前進到下一字段。一般而言,用戶必須執行的觸按越少,體驗越佳。

        下面的截圖,完全由鍵盤操作完成

        a11y

        通過元素的 :focus 偽類以及鍵盤 Tab 鍵切換焦點,用戶可以非常順暢的在脫離鼠標的情況下,對頁面的焦點切換及操作。

        然而,在許多 reset.css 中,經常能看到這樣一句 CSS 樣式代碼,為了樣式的統一,消除了可聚焦元素的 :focus 偽類:

        :focus {
            outline: 0;
        }

        我們給上述操作的代碼。也加上這樣一句代碼,全程再用鍵盤操作一下

        a11y2

        除了在 input 框有光標提示,當使用 Tab 進行焦點切換到 select 或者到 button 時,由于沒有了 :focus 樣式,用戶將完全懵逼,不知道頁面的焦點現在處于何處。

        保證非鼠標用戶體驗,合理運用 :focus-visible

        當然,造成上述結果很重要的一個原因在于。:focus 偽類不論用戶在使用鼠標還是使用鍵盤,只要元素獲焦,就會觸發。

        而其本身的默認樣式又不太能被產品或者設計接受,導致了很多人會在焦點元素觸發 :focus 偽類時,通過改變 border 的顏色或者其他一些方式替代或者直接禁用。而這樣做,從可訪問性的角度來看,對于非鼠標用戶,無疑是災難性的。

        基于此,在W3 CSS selectors-4 規范 中,新增了一個非常有意思的 :focus-visible 偽類。

        :focus-visible:這個選擇器可以有效地根據用戶的輸入方式(鼠標 vs 鍵盤)展示不同形式的焦點。

        有了這個偽類,就可以做到,當用戶使用鼠標操作可聚焦元素時,不展示 :focus 樣式或者讓其表現較弱,而當用戶使用鍵盤操作焦點時,利用 :focus-visible,讓可獲焦元素獲得一個較強的表現樣式。

        看個簡單的 Demo:

        <button>Test 1</button>
        button:active {
          background: #eee;
        }
        button:focus {
          outline: 2px solid red;
        }

        使用鼠標點擊:

        a11y3

        可以看到,使用鼠標點擊的時候,觸發了元素的 :active 偽類,也觸發了 :focus偽類,不太美觀。但是如果設置了 outline: none 又會使鍵盤用戶的體驗非常糟糕。嘗試使用 :focus-visible 偽類改造一下:

        button:active {
          background: #eee;
        }
        button:focus {
          outline: 2px solid red;
        }
        button:focus:not(:focus-visible) {
          outline: none;
        }

        看看效果,分別是在鼠標點擊 Button 和使用鍵盤控制焦點點擊 Button:

        a11y4

        CodePen Demo -- :focus-visible example

        可以看到,使用鼠標點擊,不會觸發 :foucs,只有當鍵盤操作聚焦元素,使用 Tab 切換焦點時,outline: 2px solid red 這段代碼才會生效。

        這樣,我們就既保證了正常用戶的點擊體驗,也保證了一批無法使用鼠標的用戶的焦點管理體驗。

        值得注意的是,有同學會疑惑,這里為什么使用了 :not 這么繞的寫法而不是直接這樣寫呢:

        button:focus {
          outline: unset;
        }
        button:focus-visible {
          outline: 2px solid red;
        }

        為的是兼容不支持 :focus-visible 的瀏覽器,當 :focus-visible 不兼容時,還是需要有 :focus 偽類的存在。

        使用 WAI-ARIA 規范增強語義 -- div 等非可獲焦元素模擬獲焦元素

        還有一個非常需要注意的點。

        現在很多前端同學在前端開發的過程中,喜歡使用非可獲焦元素模擬獲焦元素,譬如:

        • 使用 div 模擬 button 元素
        • 使用 ul 模擬下拉列表 select 等等

        當下很多組件庫都是這樣做的,譬如 element-ui 和 ant-design。

        在使用非可獲焦元素模擬獲焦元素的時候,一定要注意,不僅僅只是外觀長得像就完事了,其行為表現也需要符合原本的 button、select 等可聚焦元素的性質,能夠體現元素的語義,能夠被聚焦,能夠通過 Tab 切換等等。

        基于大量類似的場景,有了 WAI-ARIA 標準,WAI-ARIA是一個為殘疾人士等提供無障礙訪問動態、可交互Web內容的技術規范。

        簡單來說,它提供了一些屬性,增強標簽的語義及行為:

        • 可以使用 tabindex 屬性控制元素是否可以聚焦,以及它是否/在何處參與順序鍵盤導航
        • 可以使用 role 屬性,來標識元素的語義及作用,譬如使用 <div id="saveChanges" tabindex="0" role="button">Save</div> 來模擬一個按鈕
        • 還有大量的 aria-* 屬性,表示元素的屬性或狀態,幫助我們進一步地識別以及實現元素的語義化,優化無障礙體驗

        使用工具查看標簽的語義

        我們來看看 Github 頁面是如何定義一個按鈕的,以 Github Issues 頁面的 Edit 按鈕為例子:

        image

        這一塊,清晰的描述了這個按鈕在可訪問性相關的一些特性,譬如 Contrast 色彩對比度,按鈕的描述,也就是 Name,是給屏幕閱讀器看到的,Role 標識是這個元素的屬性,它是一個按鈕,Keyboard focusable 則表明他能否被鍵盤的 Tab 按鈕給捕獲。

        分析使用非可聚焦元素模擬的按鈕

        這里,我隨便選取了我們業務中一個使用 span 模擬按鈕的場景,是一個面包屑導航,點擊可進行跳轉,發現慘不忍睹:

        image

        HTML 代碼:

        <span class="ssc-breadcrumb-item-link"> Inbound </span>

        image

        基本上可訪問性為 0,作為一個按鈕,它不可被聚焦,無法被鍵盤用戶選中,沒有具體的語義,色彩對比度太低,可能視障用戶無法看清。并且,作為一個能進行頁面跳轉的按鈕,它沒有不是 a 標簽,沒有 href 屬性。

        即便對于面包屑導航,我們可以不將它改造成 <a> 標簽,也需要做到最基本的一些可訪問性改造:

        <span role="button" aria-label="goto inbound page" tabindex="0" class="ssc-breadcrumb-item-link"> Inbound </span>

        不要忘了再改一下顏色,達到最低色彩對比度以上,再看看:

        image

        OK,這樣,一個最最最基本的,滿足最低可訪問性需求的按鈕算是勉強達標,當然,這個按鈕可以再更進一步進行改造,涉及了更深入的可訪問性知識,本文不深入展開。

        分析組件庫的 A11Y

        最后,在我們比較常用的 Vue - element-ui、React - ant-design 中,我們來看看 ant-design 在提升可訪問性相關的一些功能。

        以 Select 選擇框組件為例,ant-design 利用了大量的 WAI-ARIA 屬性,使得用 div 模擬的下拉框不僅僅在表現上符合一個下拉框,在語義、行為上都符合一個下拉框,簡單的一個例子:

        image

        看看使用 div 模擬下拉框的 DOM 部分:

        image

        再看看在交互體驗上:

        a11y5

        上述操作全是在鍵盤下完成,看著平平無奇,實際上組件庫在正常響應可獲焦元素切換的同時,給用 div 模擬的 select 加了很多鍵盤事件的響應,可以利用回車,上下鍵等對可選項進行選擇。其實是下了很多功夫。

        對于 A11Y 相關的內容,篇幅及內容非常之多,本文無法一一展開,感興趣的可以通讀下下列文章:

        總結一下

        本文從頁面展示、交互細節、可訪問性三個大方面入手,羅列一些在實際的開發過程中,積攢的一些有益的經驗。雖然不夠全面,不過從一開始也就沒想著大而全,主要是一些可能有用但是容易被忽視的點,也算是一個不錯的查缺補漏小指南。

        當然,很多都是我個人的觀點想法,可能有一些理解存在一些問題,一些概念沒有解讀到位,也希望大家幫忙指出。

        最后

        本文到此結束,希望對你有幫助 :)

        想 Get 到最有意思的 CSS 資訊,千萬不要錯過我的公眾號 -- iCSS前端趣聞 ??

        gzh_small.png

        更多精彩 CSS 技術文章匯總在我的 Github -- iCSS ,持續更新,歡迎點個 star 訂閱收藏。

        如果還有什么疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

        查看原文

        贊 77 收藏 51 評論 7

        袁鈺涵 贊了文章 · 3月5日

        K8s微服務自動化部署容器(Rancher流水線)

        一、背景

        最近公司上線辦公網零信任安全網關系統,由我負責部署上線,在部署的時候同時也在想如何保障穩定性,以及后續部署的簡便性;

        想起了k8s微服務的成熟方案,不僅可以自動重啟還可以監控容器運行狀態,也可以集成自動化部署,于是找了一些資料將之前接觸過的rancher用了起來,首先要做的就是簡化安裝方式,下面是我的一些過程,同時也可以給大家提供參考。

        二、操作步驟

        1. 讓Rancher能訪問GitLab
        2. 在流水線添加項目
        3. 在倉庫添加必備文件
        4. CICD自動部署調試

        三、gitlab添加oauth授權

        在進入集群的命名空間中,可以在菜單欄點擊工具-流水線,然后就可以看到如下圖所示的界面

        接下來打開gitlab,然后打開設置頁面http://xx.xx.xx.xx/admin/applications/4,如下圖所示

        在上圖中將所需信息填寫進去,然后點擊保存

        保存之后,gitlab會生成Application IdSecret,我們將它復制出來,

        復制出來之后,切回rancher系統中,將其一一填寫進來,如下圖所示

        點擊完成后,會有一個彈窗進行授權,授權完成后rancher就可以訪問到gitlab倉庫了。

        四、在rancher中添加代碼倉庫

        在確保rancher可以訪問gitlab倉庫之后,在rancher菜單欄點擊工具-流水線,將需要自動化部署的項目啟用并保存,如下圖所示

        保存之后,回到CICD列表中,可以看到兩個已經啟用的項目,如下圖所示

        五、添加部署必備個文件

        接下來就可以開始在代碼中啟用CICD自動化部署了,需要在項目根目錄添加三個文件,分別是:

        1. .rancher-pipeline.yml
        2. Dockerfile
        3. deployment.yaml

        5.1 設置發布流程

        自動部署首先需要確定部署流程,主要用到文件.rancher-pipeline.yml,這里我是golang 的項目,使用了三個流程。

        首先編譯項目;接著構建鏡像推送到rancher的鏡像倉庫中,最后使用容器編排文件發布項目,配置代碼核心關注點如下圖紅色區域所示

        stages:
        - name: Build
          steps:
          - runScriptConfig:
              image: golang:1.16
              shellScript: |-
                go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct
                go mod tidy
                pwd
                go build -o ./bin/funfecenter
        - name: Publish
          steps:
          - publishImageConfig:
              dockerfilePath: ./Dockerfile
              buildContext: .
              tag: funfecenter:${CICD_EXECUTION_SEQUENCE}
        - name: Deploy
          steps:
          - applyYamlConfig:
              path: ./deployment.yaml
        timeout: 60
        notification: {}
        

        5.2 構建鏡像

        在上一步中已經將項目編譯好,接著就需要將編譯好的可執行文件放入到鏡像中,這里起作用的主要是Dockerfile文件,配置代碼比較簡單,如下所示

        FROM golang:1.16
        EXPOSE 1333
        COPY ./bin/funfecenter /data/funfecenter/center
        COPY ./init/ /
        COPY script.py /root/
        RUN  apt update -y
        RUN  apt install -y python3
        #CMD ["python3","/root/script.py"]
        CMD ["/data/funfecenter/center"]

        5.3 容器編排

        上一步已經將需要運行的鏡像推送到rancher的鏡像倉庫之后,接下來就需要構建pod來運行容器,這里發揮作用的主要是deployment.yaml 文件。

        這個文件如果沒有接觸過k8s的同學可能會比較陌生,這里我將每一行都寫了注釋,并將需要修改的地方用紅色標記圈出來了,如下圖所示

        參考配置如下所示

        kind: Service      # 指定創建資源的角色/類型
        apiVersion: v1     # 指定api版本,此值必須在kubectl api-versions中
        metadata:          # 資源的元數據/屬性
          name: funfe-center    # 資源的名字,在同一個namespace中必須唯一
        spec:               # 資源規范字段
          selector:         # 選擇器
            app: center
          type: NodePort    # 端口類型
          ports:
            - protocol: TCP     # 協議
              port: 80          # service 端口
              targetPort: 80    # 容器暴露的端口
        ---
        apiVersion: apps/v1   # 指定api版本,此值必須在kubectl api-versions中
        kind: Deployment      # 指定創建資源的角色/類型
        metadata:             # 資源的元數據/屬性
          name: funfe-center    # 資源的名字,在同一個namespace中必須唯一
          namespace: default    # 資源的名字,在同一個namespace中必須唯一
        spec:                 # 資源規范字段
          replicas: 1         # 聲明副本數目
          selector: # 選擇器
            matchLabels: # 匹配標簽
              app: center
          template:           # 模版
            metadata: # 資源的元數據/屬性
              labels: # 設定資源的標簽
                app: center
            spec:             # 資源規范字段
              imagePullSecrets:                    # 鏡像倉庫拉取密鑰
                - name: pipeline-docker-registry
              containers:
                - name: funfe                  # 容器的名字
                  image: ${CICD_IMAGE}:${CICD_EXECUTION_SEQUENCE}   # 容器使用的鏡像地址
                  ports:
                    - containerPort: 80               # 容器開發對外的端口
                    

        六、修改代碼自動部署

        修改代碼后會自動執行編譯、推送到鏡像庫、拉取代碼部署

        現在我修改代碼倉庫的代碼,回到rancher流水線中,就看到有一個任務在執行自動部署流程,如下圖所示

        執行完成周,回到集群的工作負載當中,就可以看到已經有一個服務自動化部署到K8s中


        日期:2021-03-04

        作者:湯青松

        微信:songboy8888

        查看原文

        贊 8 收藏 7 評論 2

        袁鈺涵 贊了文章 · 3月5日

        前端常用插件utils匯總

        工具庫 || 數據處理

        underscore - JavaScript的實用程序帶庫

        lodash - 是一個一致性、模塊化、高性能的 JavaScript 實用工具庫。

        表單驗證

        async-validator – 驗證表單異步

        ---jquery

        jquery-validation - 您現有的表單提供了插入式驗證,同時使各種自定義適合您的應用程序變得非常容易

        圖片懶加載

        ---JavaScript

        lazyload - 用于延遲加載圖像

        lazyload - LazyLoad是一種快速,輕巧和靈活的腳本,通過僅在內容圖像,視頻和iframe進入視口時加載它們來加快Web應用程序的速度

        ---vue

        vue-lazyload - 一個Vue.js插件,用于將圖像或組件延遲加載到應用程序中。

        ---react

        react-lazyload - 延遲加載組件,圖像或任何與性能有關的內容

        圖片預覽

        類似朋友圈

        PhotoSwipe- 適用于移動和pc,模塊化,框架獨立的JavaScript圖像庫

        滿足聊天遞增圖片的需求

        viewerjs - JavaScript圖像查看器

        fancybox - jQuery lightbox腳本,用于顯示圖像,視頻等。觸摸啟用,響應迅速且可完全自定義

        ---vue

        vue-picture-preview - 移動端、PC 端 Vue.js 圖片預覽插件

        文件上傳

        ---JavaScript

        DropzoneJS - 提供帶有圖像預覽的拖放文件上傳

        Web Uploader - 以HTML5為主的現代文件上傳組件(百度)

        jQuery-File-Upload - 具有多個文件選擇,拖放支持,進度條,驗證和預覽圖像,jQuery的音頻和視頻。

        ---vue

        vue-dropzone - Dropzone.js的Vue.js組件-帶有圖像預覽的拖放文件上傳實用程序

        單選框/復選框相關

        ---jquery

        iCheck – 增強復選框和單選按鈕

        選擇框

        Choices - 輕量級庫,用于制作高度可自定義的選擇框、文本區域和其他輸入表單。類似于select2和selectize,但沒有依賴jquery。

        Chosen - 一個使較長的笨拙的選擇框更友好的庫。

        Select2 - 基于jQuery的選擇框的替代品。它支持搜索,遠程數據集和結果的無限滾動。

        bootstrap-select - jQuery插件通過直觀的多選,搜索等功能將選擇元素帶入21世紀

        tree樹形

        ---jquery

        Bootstrap Tree View - Tree View for Twitter Bootstrap

        zTree - 一個依靠 jQuery 實現的多功能 “樹插件”

        無限滾動

        Infinite Scroll - 自動添加下一頁

        ---vue

        vue-infinite-scroll - vue.js的無限滾動指令

        列表拖拽

        Sortable - 是一個JavaScript庫,用于在現代瀏覽器和觸摸設備上對拖放列表進行重新排序

        MDN拖拽文檔

        ---vue

        Vue.Draggable - 基于Sortable.js的Vue拖放組件

        ---react

        react-sortablejs - 在成熟的拖放庫Sortable之上構建的React組件

        元素拖曳

        draggabilly - 使該shiz可拖動

        自定義滾動條

        jScrollPane-跨瀏覽器自定義滾動條

        perfect-scrollbar - 簡約但完美的自定義滾動條插件

        進度條

        nprogress - 對于細長的進度條,例如YouTube,Medium等上的進度條

        ---github類似頁面進度條

        vue-progressbar - vue的輕量級進度條

        cookie管理

        js-cookie - 一個簡單,輕巧的JavaScript API,用于處理瀏覽器cookie

        WebSocket

        ws - 簡單易用,為Node.js開創了經過快速且經過全面測試的WebSocket客戶端和服務器

        socket.io - 實時應用程序框架

        ---vue

        Vue-Socket.io - Vuejs和Vuex的Socket.io實現

        JavaScript 動畫效果庫

        Vue官網推薦

        Velocity - 是一個簡單易用、高性能、功能豐富的輕量級JS動畫庫

        Animate.css - 一款強大的預設css3動畫庫

        tween.js - JavaScript補間引擎可簡化動畫

        額外效果

        transformjs - 騰訊使CSS3轉換超級容易

        scenejs

        midnight.js - 文字顏色隨著背景變

        vue-countTo - 在指定的持續時間內計入目標數量

        CSS shake - 抖動動畫

        輪播效果

        swiper5 - 純javascript打造的滑動特效插件,面向手機、平板電腦等移動終端

        iSlider - 適用于Mobile WebApp,HTML5 App,Hybrid App的平滑移動觸摸滑塊

        ---vue

        vue-awesome-swiper - 基于Swiper4,適用于Vue的輪播組件

        瀑布流展示

        scrollreveal - 在元素滾動到視圖時對其進行動畫處理

        scrollreveal 的演示地址

        pc桌面級通知

        push.js - 世界上最通用的桌面通知框架

        響應式媒體查詢

        react中antd框架grid組件使用

        enquire.js - JavaScript中的媒體查詢

        操作引導js工具

        Intro.js - 為您的網站和項目提供新功能介紹和逐步用戶指南的更好方法

        driver.js —— 前端操作引導js工具

        編輯器工具

        editor.js - 具有干凈JSON輸出的塊樣式編輯器

        UEditor - 由百度web前端研發部開發所見即所得富文本web編輯器

        Markdown編輯器

        SimpleMDE-Markdown - 一個簡單,美觀,可嵌入的JavaScript Markdown編輯器

        simditor - 方便快捷的所見即所得編輯器

        ---想掘金一樣的markdown解析器(雙欄)

        marked – markdown解析器

        視頻

        flv.js - 用純JavaScript編寫的HTML5 Flash Video(FLV)播放器(bilibili)

        video.js - Video.js是專為HTML5世界打造的網絡視頻播放器。它支持HTML5和Flash視頻

        html5media - 簡單的h5player,輕量級

        jwplayer - 被大量網站使用

        日期選擇 || 日歷

        Pikaday - 令人耳目一新的JavaScript Datepicker —輕巧,沒有依賴關系,模塊化CSS

        bootstrap-datepicker - Twitter引導程序(@twbs)的 日期選擇器

        時間選擇

        有vue的版本,也支持日期選擇

        Flatpickr - 輕巧,功能強大的javascript datetimepicker,無依賴項

        時間處理

        Moment.js - 在JavaScript中解析,驗證,操作和顯示日期和時間。

        模擬數據

        Mock - 模擬數據生成器

        代碼注釋

        JSDoc - 是JavaScript的API文檔生成器

        觸摸方面 || 手勢

        PhyTouch - 騰訊絲般順滑的觸摸運動方案

        hammer.js - 一個用于多點觸摸手勢的javascript庫

        better-scroll - 受iscroll的啟發,它支持更多功能并具有更好的滾動性能

        loading加載動畫

        loaders.css - 令人愉悅且注重性能的純CSS加載動畫

        css-spinners - 使用CSS和最少的HTML標記制作的簡單CSS旋轉器和控件

        progressbar.js - 反應靈敏的進度條

        通知組件

        notie - 干凈,簡單的javascript通知,輸入和選擇套件,沒有依賴項

        全屏滾動

        fullPage.js - 輕松創建全屏滾動網站

        pagePiling.js - Alvaro Trigo的pagePiling插件。創建滾動顯示的部分

        fullPage.js - 專注于移動端

        ---vue

        Vue-fullpage.js - fullPage.js的官方Vue.js包裝器

        模板引擎

        sodajs - 騰訊重量輕但功能強大的JavaScript模板引擎

        list || table組件

        vue-easytable - vue table 組件,支持 單元格合并、單元格編輯、多表頭固定、多列固定、列拖動、排序、自定義列、條件過濾、分頁

        list.js - 完美的庫,可為表,列表和各種HTML元素添加搜索,排序,過濾器和靈活性

        瀏覽器屬性

        bowser - 瀏覽器屬性,例如名稱,版本,渲染引擎

        excel處理

        sheetjs - 電子表格數據工具包

        exceljs - 讀取,操作并將電子表格數據和樣式寫入XLSX和JSON。

        網格布局庫

        Masonry - 級聯網格布局庫

        FFmpeg(c模塊 視頻幀預覽)

        ffmpeg

        WebAssembly | MDN

        地圖

        Leaflet - 適用于移動設備的交互式地圖的JavaScript庫

        表情包擴展

        twemoji - 一個簡單的庫,可在所有平臺上提供標準Unicode 表情符號支持。

        瀏覽器測試

        cypress - 對瀏覽器中運行的所有內容進行快速,輕松和可靠的測試

        瀏覽器代碼編輯器

        []monaco-editor - 基于瀏覽器的代碼編輯器](https://github.com/microsoft/...

        滑動拼圖驗證

        vue-puzzle-verification
        vue-puzzle-vcode

        值得學習庫

        weui

        額外推薦

        turn.js - 做一本書,帶漂亮的翻頁的效果

        查看原文

        贊 56 收藏 49 評論 0

        袁鈺涵 發布了文章 · 3月3日

        NSA 5G 單模手機無法接收 5G信號?原因是基站進行了升級

        據澎湃新聞報道,有四川資陽手機用戶反映小米 9 Pro 5G 手機在啟用 5G 網絡情況下經常接收不到 5G 信號,目前只能被迫使用 4G。

        對電信公司進行反饋后,得到的回復是原 NSA 模式的 5G 基站已升級為 SA 模式的 5G 基站,用戶所購是單模 NSA 5G 模式手機,無法兼容升級后的 SA 5G 模式。

        雖許多城市基站仍是 NSA,但最終基站將升級為 SA 模式是毋庸置疑的,NSA 與 SA 在基站升級后無法兼容,手機使用過程中注重 5G 體驗的用戶需要在購買時看清楚手機相關信息,以免出現無法使用手機 5G 模式的事件。

        這里建議選擇雙模模式手機,這樣無論所處城市基站是是 NSA 還是 SA 模式都不影響接收 5G 信號。

        NSA 與 SA 有什么區別

        NSA 與 SA 早在 2019 年被網友以“真假”5G 進行討論,當時 SA 成為中國電信率先規模商用的 5G 服務,又有用戶發現 NSA 5G 不夠穩定,容易掉落至 4G,于是引發討論。

        NSA 是 Non-Standalone 的縮寫,稱為非獨立組網,SA 是 Standalone 縮寫,兩者不分真假,前者是同一個核心網組網架構同時承載 4G 與 5G,后者則是用兩個核心網組網架構分別承載 4G 與 5G。

        2019 年 5G 走入千家萬戶之時,基站技術研發仍然還在發展中,核心網的組網架構可做到同時承載 4G 與 5G,這是最快讓用戶用上 5G 的方式,由于 NSA 基站的普及性,NSA 5G 模式是大部分手機的配置,但 NSA 的缺點也顯示了出來,用戶接收的 5G 信號對比 SA 模式而言不夠穩定。

        SA 模式需要單獨的核心網組網架構,雖連接穩定,使用這一模式,基站建設的資金將會大幅度提高,考慮到這一原因,許多人把 NSA 看作是走向 SA 的過渡,認為在未來幾年仍然會是 NSA 的 5G 模式占多數。

        (當時文章對 5G 基站建設的預測)

        SA 的覆蓋速度超乎想象

        預測是一回事,實際上各個城市實現 SA 覆蓋的速度遠比人們預料的要快,2020 年 8 月深圳便完成了 SA 的全覆蓋,北京與上海也在逐步趕上,根據四川資陽手機用戶 NSA 單模手機無法接收 5G 信號可見四川的基站建設也在進行中。

        (深圳 SA 全覆蓋)

        (北京基站建設也在趕上)

        (上海向 SA 建設更邁一步)

        各個城市的建設逐步落實,此時距離中國 5G 手機商用不過兩年,在 SA 基站的建設上一如既往地展現著中國速度。

        這時問題又出來了,如果選擇購買 SA 5G 模式的手機,用戶所在城市的建設速度較慢,豈不是在很長的一段時間中還是無法享受 5G 帶來的便利。

        所以為了避免踩坑,并且讓自己的手機在各處皆可連上 5G,購買手機時選擇雙模 5G 是最保險的做法,雙模 5G 能接收來自 NSA 的信號,也可接收 SA 的信號,目前也越來越多的手機推出雙模模式,在手機介紹詳情列表中便可看到相關信息。

        (手機 5G 參數介紹,底下是雙模 5G)

        同時,在 SA 的建設下,部分 NSA 單模 5G 手機將會出現降價售賣模式,如果用戶本身對 5G 沒有追求,可以趁此機會進行手機購買,各人有各人的喜歡,根據自身需求進行購買即可。

        相關資料來源:

        https://www.donews.com/news/detail/1/3138699

        https://baijiahao.baidu.com/s?id=1670259566087369469

        查看原文

        贊 0 收藏 0 評論 0

        袁鈺涵 贊了文章 · 3月3日

        我最喜歡的 12 個VSCode 插件!

        原者:Katherine Peterson
        譯者:前端小智
        來源:dev
        點贊再看,微信搜索大遷世界,B站關注前端小智這個沒有大廠背景,但有著一股向上積極心態人。本文 GitHubhttps://github.com/qq44924588... 上已經收錄,文章的已分類,也整理了很多我的文檔,和教程資料。

        VSCode 之所以是如此出色的代碼編輯器,其原因之一是由社區創建的龐大的插件庫,從而提高了開發人員的工作效率。 以下是一些我最喜歡的VSCode 插件。

        1. Rainbow Brackets

        地址:https://marketplace.visualstu...

        image.png

        這個插件讓我們的括號變成五顏六色,這樣很容易就能找到匹配的對。

        2. Auto Rename Tag

        地址:https://marketplace.visualstu...

        image

        重命名一個HTML / XML標簽時,自動重命名配對的HTML / XML標簽。

        3. Relative Path

        地址:https://marketplace.visualstu...

        image

        此插件節省了我很多時間來編寫導入語句。 使用簡單的鍵盤快捷鍵即可輕松獲取工作區中任何文件的相對路徑。

        4. Prettier

        地址:https://marketplace.visualstu...

        image

        和esLint不同在于,ESLint只是一個代碼質量工具(確保沒有未使用的變量、沒有全局變量,等等)。而 Prettier 只關心格式化文件(最大長度、混合標簽和空格、引用樣式等)??梢?,代碼格式統一的問題,交給 Prettirer 再合適不過了。和 Eslint 配合使用,風味更佳。

        5. htmltagwrap

        地址:https://marketplace.visualstu...

        image

        可以在選中HTML標簽中外面套一層標簽。

        6. Markdown Preview Enhanced

        地址:ttps://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced

        image.png

        如果你寫過markdown文件,有一個實時預覽是非常有用的。

        7. Polacode

        地址:https://marketplace.visualstu...

        image.png

        這個插件可以將你的代碼保存成圖片分享給別人!

        8. Random Everything

        地址:https://marketplace.visualstu...

        image

        這個插件可以根據數據類型自動生成隨機數據,特別適合生成測試數據。

        9. CSS Peek

        地址:https://marketplace.visualstu...

        image

        CSS Peek插件擴展了HTML和ejs代碼編輯功能,支持在源代碼中的字符串中找到css/scss/less(類和id)。這在很大程度上是受方括號中稱為CSS內聯編輯器的類似功能的啟發。

        10. Turbo Console Log

        地址:https://marketplace.visualstu...

        101010.gif

        快捷添加 console.log,一鍵 注釋 / 啟用 / 刪除 所有 console.log。

        簡單說下這個插件要用到的快捷鍵:

        ctrl + alt + l 選中變量之后,使用這個快捷鍵生成 console.log
        alt + shift + c 注釋所有 console.log
        alt + shift + u 啟用所有 console.log
        alt + shift + d 刪除所有 console.log

        11. Simple React Snippets

        地址:https://marketplace.visualstu...

        上傳中...

        快速生成 React 模板片段~

        12. Snippet Creator

        地址:ttps://marketplace.visualstudio.com/items?itemName=ryanolsonx.snippet-creator

        image

        有許多代碼段擴展,如上面的React,但有時我們可能想要制作自己的自定義代碼段,這個插件可以讓你輕松做到這一點。

        ~完,我是小智,我要去刷碗了~


        原文:https://dev.to/katherinecodes...

        代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。

        交流

        文章每周持續更新,可以微信搜索【大遷世界 】第一時間閱讀,回復【福利】有多份前端視頻等著你,本文 GitHub https://github.com/qq449245884/xiaozhi 已經收錄,歡迎Star。

        查看原文

        贊 21 收藏 14 評論 0

        袁鈺涵 贊了文章 · 3月1日

        前后端接口鑒權全解 Cookie/Session/Token 的區別

        原文鏈接:前后端接口鑒權全解

        不知不覺也寫得比較長了,一次看不完建議收藏夾!本文主要解釋與請求狀態相關的術語(cookie、session、token)和幾種常見登錄的實現方式,希望大家看完本文后可以有比較清晰的理解,有感到迷惑的地方請在評論區提出。

        Cookie

        眾所周知,http 是無狀態協議,瀏覽器和服務器不可能憑協議的實現辨別請求的上下文。

        于是 cookie 登場,既然協議本身不能分辨鏈接,那就在請求頭部手動帶著上下文信息吧。

        舉個例子,以前去旅游的時候,到了景區可能會需要存放行李,被大包小包壓著,旅游也不開心啦。在存放行李后,服務員會給你一個牌子,上面寫著你的行李放在哪個格子,離開時,你就能憑這個牌子和上面的數字成功取回行李。

        cookie 做的正是這么一件事,旅客就像客戶端,寄存處就像服務器,憑著寫著數字的牌子,寄存處(服務器)就能分辨出不同旅客(客戶端)。

        你會不會想到,如果牌子被偷了怎么辦,cookie 也會被偷嗎?確實會,這就是一個很常被提到的網絡安全問題——CSRF??梢栽?a rel="nofollow noreferrer">這篇文章了解關于 CSRF 的成因和應對方法。

        cookie 誕生初似乎是用于電商存放用戶購物車一類的數據,但現在前端擁有兩個 storage(local、session),兩種數據庫(websql、IndexedDB),根本不愁信息存放問題,所以現在基本上 100% 都是在連接上證明客戶端的身份。例如登錄之后,服務器給你一個標志,就存在 cookie 里,之后再連接時,都會自動帶上 cookie,服務器便分清誰是誰。另外,cookie 還可以用于跟蹤一個用戶,這就產生了隱私問題,于是也就有了“禁用 cookie”這個選項(然而現在這個時代禁用 cookie 是挺麻煩的事情)。

        設置方式

        現實世界的例子明白了,在計算機中怎么才能設置 cookie 呢?一般來說,安全起見,cookie 都是依靠 set-cookie 頭設置,且不允許 JavaScript 設置。

        Set-Cookie: <cookie-name>=<cookie-value>
        Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
        Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit>
        Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
        Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
        Set-Cookie: <cookie-name>=<cookie-value>; Secure
        Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly
        
        Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Strict
        Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Lax
        Set-Cookie: <cookie-name>=<cookie-value>; SameSite=None; Secure
        
        // Multiple attributes are also possible, for example:
        Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly

        其中 <cookie-name>=<cookie-value> 這樣的 kv 對,內容隨你定,另外還有 HttpOnly、SameSite 等配置,一條 Set-Cookie 只配置一項 cookie。

        • Expires 設置 cookie 的過期時間(時間戳),這個時間是客戶端時間。
        • Max-Age 設置 cookie 的保留時長(秒數),同時存在 Expires 和 Max-Age 的話,Max-Age 優先
        • Domain 設置生效的域名,默認就是當前域名,不包含子域名
        • Path 設置生效路徑,/ 全匹配
        • Secure 設置 cookie 只在 https 下發送,防止中間人攻擊
        • HttpOnly 設置禁止 JavaScript 訪問 cookie,防止XSS
        • SameSite 設置跨域時不攜帶 cookie,防止CSRF

        Secure 和 HttpOnly 是強烈建議開啟的。SameSite 選項需要根據實際情況討論,因為 SameSite 可能會導致即使你用 CORS 解決了跨越問題,依然會因為請求沒自帶 cookie 引起一系列問題,一開始還以為是 axios 配置問題,繞了一大圈,然而根本沒關系。

        其實因為 Chrome 在某一次更新后把沒設置 SameSite 默認為 Lax,你不在服務器手動把 SameSite 設置為 None 就不會自動帶 cookie 了。

        發送方式

        參考 MDN,cookie 的發送格式如下(其中 PHPSESSID 相關內容下面會提到):

        Cookie: <cookie-list>
        Cookie: name=value
        Cookie: name=value; name2=value2; name3=value3
        
        Cookie: PHPSESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1

        在發送 cookie 時,并不會傳上面提到的配置到服務器,因為服務器在設置后就不需要關心這些信息了,只要現代瀏覽器運作正常,收到的 cookie 就是沒問題的。

        Session

        從 cookie 說到 session,是因為 session 才是真正的“信息”,如上面提到的,cookie 是容器,里面裝著 PHPSESSID=298zf09hf012fh2;,這就是一個 session ID。

        不知道 session 和 session id 會不會讓你看得有點頭暈?

        當初 session 的存在就是要為客戶端和服務器連接提供的信息,所以我將 session 理解為信息,而 session id 是獲取信息的鑰匙,通常是一串唯一的哈希碼。

        接下來分析兩個 node.js express 的中間件,理解兩種 session 的實現方式。

        session 信息可以儲存在客戶端,如 cookie-session,也可以儲存在服務器,如 express-session。使用 session ID 就是把 session 放在服務器里,用 cookie 里的 id 尋找服務器的信息。

        客戶端儲存

        對于 cookie-session 庫,比較容易理解,其實就是把所有信息加密后塞到 cookie 里。其中涉及到 cookies 庫。在設置 session 時其實就是調用 cookies.set,把信息寫到 set-cookie 里,再返回瀏覽器。換言之,取值和賦值的本質都是操作 cookie。

        瀏覽器在接收到 set-cookie 頭后,會把信息寫到 cookie 里。在下次發送請求時,信息又通過 cookie 原樣帶回來,所以服務器什么東西都不用存,只負責獲取和處理 cookie 里的信息,這種實現方法不需要 session ID。

        這是一段使用 cookie-session 中間件為請求添加 cookie 的代碼:

        const express = require('express')
        var cookieSession = require('cookie-session')
        const app = express()
        app.use(
          cookieSession({
            name: 'session',
            keys: [
              /* secret keys */
              'key',
            ],
            // Cookie Options
            maxAge: 24 * 60 * 60 * 1000, // 24 hours
          })
        )
        app.get('/', function(req, res) {
          req.session.test = 'hey'
          res.json({
            wow: 'crazy',
          })
        })
        
        app.listen(3001)

        在通過 app.use(cookieSession()) 使用中間件之前,請求是不會設置 cookie 的,添加后再訪問(并且在設置 req.session 后,若不添加 session 信息就沒必要寫、也沒內容寫到 cookie 里),就能看到服務器響應頭部新增了下面兩行,分別寫入 session 和 session.sig:

        Set-Cookie: session=eyJ0ZXN0IjoiaGV5In0=; path=/; expires=Tue, 23 Feb 2021 01:07:05 GMT; httponly
        Set-Cookie: session.sig=QBoXofGvnXbVoA8dDmfD-GMMM6E; path=/; expires=Tue, 23 Feb 2021 01:07:05 GMT; httponly

        然后你就能在 DevTools 的 Application 標簽看到 cookie 成功寫入。session 的值 eyJ0ZXN0IjoiaGV5In0= 通過 base64 解碼(不了解 base64 的話可以看這里)即可得到 {"test":"hey"},這就是所謂的“將 session 信息放到客戶端”,因為 base64 編碼并不是加密,這就跟明文傳輸沒啥區別,所以請不要在客戶端 session 里放用戶密碼之類的機密信息。

        即使現代瀏覽器和服務器做了一些約定,例如使用 https、跨域限制、還有上面提到 cookie 的 httponly 和 sameSite 配置等,保障了 cookie 安全。但是想想,傳輸安全保障了,如果有人偷看你電腦里的 cookie,密碼又恰好存在 cookie,那就能無聲無息地偷走密碼。相反的,只放其他信息或是僅僅證明“已登錄”標志的話,只要退出一次,這個 cookie 就失效了,算是降低了潛在危險。

        說回第二個值 session.sig,它是一個 27 字節的 SHA1 簽名,用以校驗 session 是否被篡改,是 cookie 安全的又一層保障。

        服務器儲存

        既然要儲存在服務器,那么 express-session 就需要一個容器 store,它可以是內存、redis、mongoDB 等等等等,內存應該是最快的,但是重啟程序就沒了,redis 可以作為備選,用數據庫存 session 的場景感覺不多。

        express-session 的源碼沒 cookie-session 那么簡明易懂,里面有一個有點繞的問題,req.session 到底是怎么插入的?

        不關注實現可以跳過這段,有興趣的話可以跟著思路看看 express-session 的源碼。

        我們可以從 .session = 這個關鍵詞開始找,找到:

        • store.generate 否決這個,容易看出這個是初始化使用的
        • Store.prototype.createSession 這個是根據 req 和 sess 參數在 req 中設置 session 屬性,沒錯,就是你了

        于是全局搜索 createSession,鎖定 index 里的 inflate (就是填充的意思)函數。

        最后尋找 inflate 的調用點,是使用 sessionID 為參數的 store.get 的回調函數,一切說得通啦——

        在監測到客戶端送來的 cookie 之后,可以從 cookie 獲取 sessionID,再使用 id 在 store 中獲取 session 信息,掛到 req.session,經過這個中間件,你就能順利地使用 req 中的 session。

        那賦值怎么辦呢?這就和上面儲存在客戶端不同了,上面要修改客戶端 cookie 信息,但是對于儲存在服務器的情況,你修改了 session 那就是“實實在在地修改”了嘛,不用其他花里胡哨的方法,內存中的信息就是修改了,下次獲取內存里的對應信息也是修改后的信息。(僅限于內存的實現方式,使用數據庫時仍需要額外的寫入)

        在請求沒有 session id 的情況下,通過 store.generate 創建新的 session,在你寫 session 的時候,cookie 可以不改變,只要根據原來的 cookie 訪問內存里的 session 信息就可以了。

        var express = require('express')
        var parseurl = require('parseurl')
        var session = require('express-session')
        
        var app = express()
        
        app.use(
          session({
            secret: 'keyboard cat',
            resave: false,
            saveUninitialized: true,
          })
        )
        
        app.use(function(req, res, next) {
          if (!req.session.views) {
            req.session.views = {}
          }
        
          // get the url pathname
          var pathname = parseurl(req).pathname
        
          // count the views
          req.session.views[pathname] = (req.session.views[pathname] || 0) + 1
        
          next()
        })
        
        app.get('/foo', function(req, res, next) {
          res.json({
            session: req.session,
          })
        })
        
        app.get('/bar', function(req, res, next) {
          res.send('you viewed this page ' + req.session.views['/bar'] + ' times')
        })
        
        app.listen(3001)

        兩種儲存方式的對比

        首先還是計算機世界最重要的哲學問題:時間和空間的抉擇。

        儲存在客戶端的情況,解放了服務器存放 session 的內存,但是每次都帶上一堆 base64 處理的 session 信息,如果量大的話傳輸就會很緩慢。

        儲存在服務器相反,用服務器的內存拯救了帶寬。

        另外,在退出登錄的實現和結果,也是有區別的。

        儲存在服務器的情況就很簡單,如果 req.session.isLogin = true 是登錄,那么 req.session.isLogin = false 就是退出。

        但是狀態存放在客戶端要做到真正的“即時退出登錄”就很困難了。你可以在 session 信息里加上過期日期,也可以直接依靠 cookie 的過期日期,過期之后,就當是退出了。

        但是如果你不想等到 session 過期,現在就想退出登錄!怎么辦?認真想想你會發現,僅僅依靠客戶端儲存的 session 信息真的沒有辦法做到。

        即使你通過 req.session = null 刪掉客戶端 cookie,那也只是刪掉了,但是如果有人曾經把 cookie 復制出來了,那他手上的 cookie 直到 session 信息里的過期時間前,都是有效的。

        說“即時退出登錄”有點標題黨的意味,其實我想表達的是,你沒辦法立即廢除一個 session,這可能會造成一些隱患。

        Token

        session 說完了,那么出現頻率超高的關鍵字 token 又是什么?

        不妨谷歌搜一下 token 這個詞,可以看到冒出來幾個(年紀大的人)比較熟悉的圖片:密碼器。過去網上銀行不是只要短信認證就能轉賬,還要經過一個密碼器,上面顯示著一個變動的密碼,在轉賬時你需要輸入密碼器中的代碼才能轉賬,這就是 token 現實世界中的例子。憑借一串碼或是一個數字證明自己身份,這事情不就和上面提到的行李問題還是一樣的嗎……

        其實本質上 token 的功能就是和 session id 一模一樣。你把 session id 說成 session token 也沒什么問題(Wikipedia 里就寫了這個別名)。

        其中的區別在于,session id 一般存在 cookie 里,自動帶上;token 一般是要你主動放在請求中,例如設置請求頭的 Authorizationbearer:<access_token>。

        然而上面說的都是一般情況,根本沒有明確規定!

        劇透一下,下面要講的 JWT(JSON Web Token)!他是一個 token!但是里面放著 session 信息!放在客戶端,并且可以隨你選擇放在 cookie 或是手動添加在 Authorization!但是他就叫 token!

        個人覺得你不能通過存放的位置判斷是 token 或是 session id,也不能通過內容判斷是 token 或是 session 信息,session、session id 以及 token 都是很意識流的東西,只要你明白他是什么、怎么用就好了,怎么稱呼不太重要。

        另外在搜索資料時也看到有些文章說 session 和 token 的區別就是新舊技術的區別,好像有點道理。

        在 session 的 Wikipedia 頁面上 HTTP session token 這一欄,舉例都是 JSESSIONID (JSP)、PHPSESSID (PHP)、CGISESSID (CGI)、ASPSESSIONID (ASP) 等比較傳統的技術,就像 SESSIONID 是他們的代名詞一般;而在研究現在各種平臺的 API 接口和 OAuth2.0 登錄時,都是使用 access token 這樣的字眼,這個區別著實有點意思。

        理解 session 和 token 的聯系之后,可以在哪里能看到“活的” token 呢?

        打開 GitHub 進入設置,找到 Settings / Developer settings,可以看到 Personal access tokens 選項,生成新的 token 后,你就可以帶著它通過 GitHub API,證明“你就是你”。

        在 OAuth 系統中也使用了 Access token 這個關鍵詞,寫過微信登錄的朋友應該都能感受到 token 是個什么啦。

        Token 在權限證明上真的很重要,不可泄漏,誰拿到 token,誰就是“主人”。所以要做一個 Token 系統,刷新或刪除 Token 是必須要的,這樣在盡快彌補 token 泄漏的問題。

        在理解了三個關鍵字和兩種儲存方式之后,下面我們正式開始說“用戶登錄”相關的知識和兩種登錄規范——JWT 和 OAuth2.0。

        接著你可能會頻繁見到 Authentication 和 Authorization 這兩個單詞,它們都是 Auth 開頭,但可不是一個意思,簡單來說前者是驗證,后者是授權。在編寫登錄系統時,要先驗證用戶身份,設置登錄狀態,給用戶發送 token 就是授權。

        JWT

        全稱 JSON Web Token(RFC 7519),是的,JWT 就是一個 token。為了方便理解,提前告訴大家,JWT 用的是上面客戶端儲存的方式,所以這部分可能會經常用到上面提到的名稱。

        結構

        雖說 JWT 就是客戶端儲存 session 信息的一種,但是 JWT 有著自己的結構:Header.Payload.Signature(分為三個部分,用 . 隔開)

        Header

        {
          "alg": "HS256",
          "typ": "JWT"
        }

        typ 說明 token 類型是 JWT,alg 代表簽名算法,HMAC、SHA256、RSA 等。然后將其 base64 編碼。

        Payload

        {
          "sub": "1234567890",
          "name": "John Doe",
          "admin": true
        }

        Payload 是放置 session 信息的位置,最后也要將這些信息進行 base64 編碼,結果就和上面客戶端儲存的 session 信息差不多。

        不過 JWT 有一些約定好的屬性,被稱為 Registered claims,包括:

        • iss (issuer):簽發人
        • exp (expiration time):過期時間
        • sub (subject):主題
        • aud (audience):受眾
        • nbf (Not Before):生效時間
        • iat (Issued At):簽發時間
        • jti (JWT ID):編號

        Signature

        最后一部分是簽名,和上面提到的 session.sig 一樣是用于防止篡改,不過 JWT 把簽名和內容組合到一起罷了。

        JWT 簽名的生成算法是這樣的:

        HMACSHA256(
          base64UrlEncode(header) + "." +
          base64UrlEncode(payload),
          secret)

        使用 Header 里 alg 的算法和自己設定的密鑰 secret 編碼 base64UrlEncode(header) + "." + base64UrlEncode(payload)

        最后將三部分通過 . 組合在一起,你可以通過 jwt.io Debugger 形象地看到 JWT 的組成原理:

        如何使用

        在驗證用戶,順利登錄后,會給用戶返回 JWT。因為 JWT 的信息沒有加密,所以別往里面放密碼,詳細原因在客戶端儲存的 cookie 中提到。

        用戶訪問需要授權的連接時,可以把 token 放在 cookie,也可以在請求頭帶上 Authorization: Bearer <token>。(手動放在請求頭不受 CORS 限制,不怕 CSRF)

        這樣可以用于自家登錄,也可以用于第三方登錄。單點登錄也是 JWT 的常用領域。

        JWT 也因為信息儲存在客戶端造成無法讓自己失效的問題,這算是 JWT 的一個缺點。

        HTTP authentication

        HTTP authentication 是一種標準化的校驗方式,不會使用 cookie 和 session 相關技術。請求頭帶有 Authorization: Basic <credentials> 格式的授權字段。

        其中 credentials 就是 Base64 編碼的用戶名 + : + 密碼(或 token),以后看到 Basic authentication,意識到就是每次請求都帶上用戶名密碼就好了。

        Basic authentication 大概比較適合 serverless,畢竟他沒有運行著的內存,無法記錄 session,直接每次都帶上驗證就完事了。

        OAuth 2.0

        OAuth 2.0(RFC 6749)也是用 token 授權的一種協議,它的特點是你可以在有限范圍內使用別家接口,也可以借此使用別家的登錄系統登錄自家應用,也就是第三方應用登錄。(注意啦注意啦,OAuth 2.0 授權流程說不定面試會考哦?。?/p>

        既然是第三方登錄,那除了應用本身,必定存在第三方登錄服務器。在 OAuth 2.0 中涉及三個角色:用戶、應用提供方、登錄平臺,相互調用關系如下:

             +--------+                               +---------------+
             |        |--(A)- Authorization Request ->|   Resource    |
             |        |                               |     Owner     |
             |        |<-(B)-- Authorization Grant ---|               |
             |        |                               +---------------+
             |        |
             |        |                               +---------------+
             |        |--(C)-- Authorization Grant -->| Authorization |
             | Client |                               |     Server    |
             |        |<-(D)----- Access Token -------|               |
             |        |                               +---------------+
             |        |
             |        |                               +---------------+
             |        |--(E)----- Access Token ------>|    Resource   |
             |        |                               |     Server    |
             |        |<-(F)--- Protected Resource ---|               |
             +--------+                               +---------------+

        很多大公司都提供 OAuth 2.0 第三方登錄,這里就拿小聾哥的微信舉例吧——

        準備

        一般來說,應用提供方需要先在登錄平臺申請好 AppID 和 AppSecret。(微信使用這個名稱,其他平臺也差不多,一個 ID 和一個 Secret)

        獲取 code

        什么是授權臨時票據(code)? 答:第三方通過 code 進行獲取 access_token 的時候需要用到,code 的超時時間為 10 分鐘,一個 code 只能成功換取一次 access_token 即失效。code 的臨時性和一次保障了微信授權登錄的安全性。第三方可通過使用 https 和 state 參數,進一步加強自身授權登錄的安全性。

        在這一步中,用戶先在登錄平臺進行身份校驗。

        https://open.weixin.qq.com/connect/qrconnect?
        appid=APPID&
        redirect_uri=REDIRECT_URI&
        response_type=code&
        scope=SCOPE&
        state=STATE
        #wechat_redirect
        參數是否必須說明
        appid應用唯一標識
        redirect_uri請使用 urlEncode 對鏈接進行處理
        response_type填 code
        scope應用授權作用域,擁有多個作用域用逗號(,)分隔,網頁應用目前僅填寫 snsapi_login
        state用于保持請求和回調的狀態,授權請求后原樣帶回給第三方。該參數可用于防止 csrf 攻擊(跨站請求偽造攻擊)

        注意一下 scope 是 OAuth2.0 權限控制的特點,定義了這個 code 換取的 token 可以用于什么接口。

        正確配置參數后,打開這個頁面看到的是授權頁面,在用戶授權成功后,登錄平臺會帶著 code 跳轉到應用提供方指定的 redirect_uri

        redirect_uri?code=CODE&state=STATE

        授權失敗時,跳轉到

        redirect_uri?state=STATE

        也就是失敗時沒 code。

        獲取 token

        在跳轉到重定向 URI 之后,應用提供方的后臺需要使用微信給你的code獲取 token,同時,你也可以用傳回來的 state 進行來源校驗。

        要獲取 token,傳入正確參數訪問這個接口:

        https://api.weixin.qq.com/sns/oauth2/access_token?
        appid=APPID&
        secret=SECRET&
        code=CODE&
        grant_type=authorization_code
        參數是否必須說明
        appid應用唯一標識,在微信開放平臺提交應用審核通過后獲得
        secret應用密鑰 AppSecret,在微信開放平臺提交應用審核通過后獲得
        code填寫第一步獲取的 code 參數
        grant_type填 authorization_code,是其中一種授權模式,微信現在只支持這一種

        正確的返回:

        {
          "access_token": "ACCESS_TOKEN",
          "expires_in": 7200,
          "refresh_token": "REFRESH_TOKEN",
          "openid": "OPENID",
          "scope": "SCOPE",
          "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
        }

        得到 token 之后你就可以根據之前申請 code 填寫的 scope 調用接口了。

        使用 token 調用微信接口

        授權作用域(scope)接口接口說明
        snsapi_base/sns/oauth2/access_token通過 code 換取 access_token、refresh_token 和已授權 scope
        snsapi_base/sns/oauth2/refresh_token刷新或續期 access_token 使用
        snsapi_base/sns/auth檢查 access_token 有效性
        snsapi_userinfo/sns/userinfo獲取用戶個人信息

        例如獲取個人信息就是 GEThttps://api.weixin.qq.com/sns...

        注意啦,在微信 OAuth 2.0,access_token 使用 query 傳輸,而不是上面提到的 Authorization。

        使用 Authorization 的例子,如 GitHub 的授權,前面的步驟基本一致,在獲取 token 后,這樣請求接口:

        curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com

        說回微信的 userinfo 接口,返回的數據格式如下:

        {
          "openid": "OPENID",
          "nickname": "NICKNAME",
          "sex": 1,
          "province":"PROVINCE",
          "city":"CITY",
          "country":"COUNTRY",
          "headimgurl":"https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
          "privilege":[ "PRIVILEGE1" "PRIVILEGE2" ],
          "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
        }

        后續使用

        在使用 token 獲取用戶個人信息后,你可以接著用 userinfo 接口返回的 openid,結合 session 技術實現在自己服務器登錄。

        // 登錄
        req.session.id = openid
        if (req.session.id) {
          //   已登錄
        } else {
          //   未登錄
        }
        // 退出
        req.session.id = null
        // 清除 session

        總結一下 OAuth2.0 的流程和重點:

        • 為你的應用申請 ID 和 Secret
        • 準備好重定向接口
        • 正確傳參獲取 code <- 重要
        • code 傳入你的重定向接口
        • 在重定向接口中使用 code 獲取 token <- 重要
        • 傳入 token 使用微信接口

        OAuth2.0 著重于第三方登錄和權限限制。而且 OAuth2.0 不止微信使用的這一種授權方式,其他方式可以看阮老師的OAuth 2.0 的四種方式。

        其他方法

        JWT 和 OAuth2.0 都是成體系的鑒權方法,不代表登錄系統就一定要這么復雜。

        簡單登錄系統其實就以上面兩種 session 儲存方式為基礎就能做到。

        1. 使用服務器儲存 session 為基礎,可以用類似 req.session.isLogin = true 的方法標志該 session 的狀態為已登錄。
        2. 使用客戶端儲存 session 為基礎,設置 session 的過期日期和登錄人就基本能用了。
        {
          "exp": 1614088104313,
          "usr": "admin"
        }

        (就是和 JWT 原理基本一樣,不過沒有一套體系)

        1. 甚至你可以使用上面的知識自己寫一個 express 的登錄系統:
        • 初始化一個 store,內存、redis、數據庫都可以
        • 在用戶身份驗證成功后,隨機生成一串哈希碼作為 token
        • 用 set-cookie 寫到客戶端
        • 再在服務器寫入登錄狀態,以內存為例就是在 store 中添加哈希碼作為屬性
        • 下次請求帶著 cookie 的話檢查 cookie 帶來的 token 是否已經寫入 store 中即可
        let store = {}
        
        // 登錄成功后
        store[HASH] = true
        cookie.set('token', HASH)
        
        // 需要鑒權的請求鐘
        const hash = cookie.get('token')
        if (store[hash]) {
          // 已登錄
        } else {
          // 未登錄
        }
        
        // 退出
        const hash = cookie.get('token')
        delete store[hash]

        總結

        以下列出本文重點:

        • cookie 是儲存 session/session id/token 的容器
        • cookie 設置一般通過 set-cookie 請求頭設置
        • session 信息可以存放在瀏覽器,也可以存放在服務器
        • session 存放在服務器時,以 session id 為鑰匙獲取信息
        • token/session/session id 三者的界限是模糊的
        • 一般新技術使用 token,傳統技術使用 session id
        • cookie/token/session/session id 都是用于鑒權的實用技術
        • JWT 是瀏覽器儲存 session 的一種
        • JWT 常用于單點登錄(SSO)
        • OAuth2.0 的 token 不是由應用端頒發,存在另外的授權服務器
        • OAuth2.0 常用于第三方應用登錄

        參考

        查看原文

        贊 71 收藏 56 評論 2

        認證與成就

        • 獲得 143 次點贊
        • 獲得 2 枚徽章 獲得 0 枚金徽章, 獲得 0 枚銀徽章, 獲得 2 枚銅徽章

        擅長技能
        編輯

        (??? )
        暫時沒有

        開源項目 & 著作
        編輯

        (??? )
        暫時沒有

        注冊于 2020-09-14
        個人主頁被 25.5k 人瀏覽

        一本到在线是免费观看_亚洲2020天天堂在线观看_国产欧美亚洲精品第一页_最好看的2018中文字幕