超強的純 CSS 滑鼠點選拖拽效果

2022-10-10 12:00:47

背景

滑鼠拖拽元素移動,算是一個稍微有點點複雜的互動。

而在本文,我們就將打破常規,向大家介紹一種超強的僅僅使用純 CSS 就能夠實現的滑鼠點選拖拽效果。

在之前的這篇文章中 -- 不可思議的純 CSS 實現滑鼠跟隨,我們介紹了非常多有意思的純 CSS 的滑鼠跟隨效果,像是這樣:

但是,可以看到,上面的效果中,元素的移動不是很絲滑。如果你瞭解上述的實現方式,就會知道它存在比較大的侷限性。

本文,我們還是僅僅通過 CSS,來實現一種絲滑的滑鼠點選拖動元素移動的效果。

滑鼠點選拖拽跟隨效果

OK,什麼意思呢?我們先來看一個最最簡單的效果示意圖,實現點選一個元素,能夠拖動元素進行移動的效果:

好的,到這裡,在繼續往下閱讀之前,你可以停一停。這種效果,正常而言,都是必須要藉助 JavaScript 才能夠實現的。從表現上來看:

  1. 首先拖拽元素過程,可以任意將元素進行移動
  2. 然後放置元素,讓元素停留在另外一個地方

思考一下,如果不借助 JavaScript 的話,有辦法將元素小球從 A 點移動到 B 點麼?這個效果完全就不像是純 CSS 能夠完成的。

答案必然是可以的!整個過程也非常之巧妙,這裡我們核心需要利用強大的 resize 屬性。以及,配合通過構建一種巧妙的佈局,去解決可能會遇到的各種難題。

使用 resize,構建可拖拽改變大小的元素

首先,我們利用 resize 屬性來實現一個可改變大小的元素。

什麼是 resize 呢?根據 MDN -- resize:該 CSS 屬性允許你控制一個元素的可調整大小性。

其 CSS 語法如下所示:

{
/* Keyword values */
  resize: none;
  resize: both;
  resize: horizontal;
  resize: vertical;
  resize: block;
  resize: inline;
}

簡單解釋一下:

  • resize: none:元素不能被使用者縮放
  • resize: both:允許使用者在水平和垂直方向上調整元素的大小
  • resize: horizontal:允許使用者在水平方向上調整元素的大小
  • resize: vertical:允許使用者在垂直方向上調整元素的大小
  • resize: block:根據書寫模式(writing-mode)和方向值(direction),元素顯示允許使用者在塊方向上(block)水平或垂直調整元素大小的機制。
  • resize: inline:根據書寫模式(writing-mode)和方向值(direction),元素顯示一種機制,允許使用者在內聯方向上(inline)水平方向或垂直方向調整元素的大小。

看一個最簡單的 DEMO:

<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. A aut qui labore rerum placeat similique hic consequatur tempore doloribus aliquid alias, nobis voluptates. Perferendis, voluptate placeat esse soluta deleniti id!</p>
p {
    width: 200px;
    height: 200px;
    resize: horizontal;
    overflow: scroll;
}

這裡,我們設定了一個長寬為 200px<p> 為橫向可拖拽改變寬度。效果如下:

簡單總結一些小技巧:

  • resize 的生效,需要配合 overflow: scroll,當然,準確的說法是,overflow 不是 visible,或者可以直接作用於替換元素譬如影象、<video><iframe><textarea>
  • 我們可以通過 resizehorizontalverticalboth 來設定橫向拖動、縱向拖動、橫向縱向皆可拖動。
  • 可以配合容器的 max-widthmin-widthmax-heightmin-height 限制可拖拽改變的一個範圍

這裡,如果你的對 resize 還有所疑惑,或者想了解更多 resize 的有趣用法,可以看看我的這篇文章:CSS 奇思妙想 | 使用 resize 實現強大的圖片拖拽切換預覽功能

將 resize 應用到本文範例中

OK,接下來,我們將 resize 實際運用到我們本文的例子中去,首先,我們先簡單實現一個 DIV:

<div class="g-resize"></div>
.g-resize {
    width: 100px;
    height: 100px;
    border: 1px solid deeppink;
}

如下,非常普通,沒有什麼特別的:

但是,通過給這個元素加上 resize: both 以及 overflow: scroll,此時,這個元素的大小就通過元素右下角的 ICON 進行拖動改變。

簡單修改下我們的 CSS 程式碼:

.g-resize {
    width: 100px;
    height: 100px;
    border: 1px solid deeppink;
    resize: both;
    overflow: scroll;
}

這樣,我們就得到了一個靈活可以拖動的元素:

是的,我們的整個效果,就需要藉助這個特性進行實現。

在此基礎上,我們可以嘗試將一個元素定位到上面這個可拖動放大縮小的元素的右下角,看著能不能實現上述的效果。

簡單加一點程式碼:

<div class="g-resize"></div>
.g-resize {
    position: relative;
    width: 20px;
    height: 20px;
    resize: both;
    overflow: scroll;
}
.g-resize::before {
    content: "";
    position: absolute;
    bottom: 0;
    right: 0;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: deeppink;
}

我們利用元素的偽元素實現了一個小球,放置在容器的右下角看看效果:

如果我們再把整個設定了 resize: both 的邊框隱藏呢?那麼效果就會是這樣:

Wow,整個效果已經非常的接近了!只是,認真看的話,能夠看到一些瑕疵,就是還是能夠看到設定了 resize 的元素的這個 ICON:

這個也好解決,在 Chrome 中,我們可以通過另外一個偽元素 ::-webkit-resizer ,設定這個 ICON 的隱藏。

根據 MDN - ::-webkit-resizer,它屬於整體的卷軸偽類樣式家族中的一員。

其中 ::-webkit-resizer 可以控制出現在某些元素底角的可拖動調整大小的滾軸的樣式。

所以,這裡我就利用這個偽類:

.g-resize {
    position: relative;
    width: 20px;
    height: 20px;
    resize: both;
    overflow: scroll;
}
.g-resize::before {
    content: "";
    position: absolute;
    bottom: 0;
    right: 0;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: deeppink;
}
.g-resize::-webkit-resizer {
    background-color: transparent;
}

這樣,這裡的核心在於利用了 .g-resize::-webkit-resizer 中的 background-color: transparent,將滾軸的顏色設定為了透明色。我們就得到了與文章一開始,一模一樣的效果:

解決溢位被裁剪問題

當然,這裡有個很致命的問題,如果需要移動的內容,遠比設定了 resize 的容器要大,或者其初始位置不在該容器內,超出了的部分因為設定了 overflow: scroll,將無法看到。

因此上述方案存在比較大的缺陷。

舉個例子,假設我們需要被拖動的元素不再是一個有這樣一個簡單的結構:

<div class="g-content"></div>
.g-content {
    width: 100px;
    height: 100px;
    background: black;
    pointer-event: none;
    
    &::before {
        content: "";
        position: absolute;
        width: 20px;
        height: 20px;
        background: yellow;
        border-radius: 50%;    
}

而像是這樣,是一個更為複雜的佈局內容展示(當然下面展示的也比較簡單,實際中可以想象成任意複雜結構內容):

如果將這個結構,扔到上面的 g-resize 中:

<div class="g-resize">
    <div class="g-content"></div>
</div>

那麼就會因為設定了 overflow: scroll 的原因,將完全看不到,只剩下一小塊:

為了解決這個問題,我們得修改原本的 DOM 結構,另闢蹊徑。

方法有很多,譬如可以利用 Grid 佈局的一些特性。當然,這裡我們只需要巧妙的加多一層,就可以完全解決這個問題。

我們來實現這樣一個佈局:

<div class="g-container">
    <div class="g-resize"></div>
    <div class="g-content"></div>
</div>

解釋一下上述程式碼,其中:

  1. g-container 設定為絕對定位加上 display: inline-block,這樣其盒子大小就可以由內部正常流式佈局盒子的大小撐開
  2. g-resize 設定為 position: relative 並且設定 resize,負責提供一個可拖動大小元素,在這個元素的變化過程中,就能動態改變父容器的高寬
  3. g-content 實際內容盒子,通過 position: absolute 定位到容器的右下角即可

看看完整的 CSS 程式碼:

.g-container {
    position: absolute;
    display: inline-block;
}
.g-resize { 
    content: "";
    position: relative;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    resize: both;
    overflow: scroll;
    z-index: 1;
}
.g-content {
    position: absolute;
    bottom: -80px;
    right: -80px;
    width: 100px;
    height: 100px;
    background: black;
    pointer-event: none;
    
    &::before {
        content: "";
        position: absolute;
        width: 20px;
        height: 20px;
        background: yellow;
        border-radius: 50%;
        transition: .3s;
    }
}
.g-container:hover .g-content::before {
    transform: scale(1.1);
    box-shadow: -2px 2px 4px -4px #333, -4px 4px 8px -4px #333;
}
.g-resize::-webkit-resizer {
    background-color: transparent;
}

下圖中,你看到的所有元素,都只是 g-content 呈現出來的元素,整個效果就是這樣:

是的,可能你會有所疑惑,下面我用簡單不同顏色,標識不同不同的 DOM 結構,方便你去理解。

  1. 紅色邊框表示整個 g-container 的大小
  2. 用藍色矩形表示設定了 g-resize 元素的大小
  3. 關掉 ::-webkit-resizer 的透明設定,展示出 resize 框的可拖拽 ICON
.g-container {
    border: 3px solid red;
}
.g-resize { 
    content: "";
    background: blue;
    resize: both;
    overflow: scroll;
}
.g-resize::-webkit-resizer {
    // background-color: transparent;
}

看看這個圖,整個原理基本就比較清晰的浮現了出來:

完整的原理程式碼,你可以戳這裡:CodePen Demo -- Pure CSS Auto Drag Demo

實際應用

OK,用了比較大篇幅對原理進行了描述。下面我們舉一個實際的應用場景。使用上述技巧製作的可拖動便籤貼。靈感來自 -- scottkellum

程式碼也不多,如果你瞭解了上面的內容,下面的程式碼將非常好理解:

<div class="g-container">
    <div class="g-resize"></div>
    <div class="g-content"> Lorem ipsum dolor sit amet consectetur?</div>
</div>

完整的 CSS 程式碼如下:

body {
    position: relative;
    padding: 10px;
    background: url("背景圖");
    background-size: cover;
}
.g-container {
    position: absolute;
    display: inline-block;
}
.g-resize {
    content: "";
    position: relative;
    width: 20px;
    height: 20px;
    resize: both;
    overflow: scroll;
    z-index: 1;
}
.g-content {
    position: absolute;
    bottom: -160px;
    right: -180px;
    color: rgba(#000, 0.8);
    background-image: linear-gradient(
        160deg,
        rgb(255, 222, 30) 50%,
        rgb(255, 250, 80)
    );
    width: 200px;
    height: 180px;
    pointer-event: none;
    text-align: center;
    font-family: "marker felt", "comic sans ms", sans-serif;
    font-size: 24px;
    line-height: 1.3;
    padding: 1em;
    box-sizing: border-box;
    &:before {
        content: "";
        position: absolute;
        width: 20px;
        height: 20px;
        top: 0;
        left: 0;
        border-radius: 50%;
        background-image: radial-gradient(
            at 60% 30%,
            #f99,
            red 20%,
            rgb(180, 8, 0)
        );
        background-position: 20% 10%;
        cursor: pointer;
        pointer-events: none;
        transform: scale(0.8);
        box-shadow: -5px 10px 3px -8.5px #000, -1px 7px 12px -5px #000;
        transition: all 0.3s ease;
        transform: scale(0.8);
    }
}
.g-container:hover .g-content::before {
    transform: scale(0.9);
    box-shadow: -5px 10px 6px -8.5px #000, -1px 7px 16px -4px #000;
}
.g-resize::-webkit-resizer {
    background-color: transparent;
}

我們通過上述的技巧,實現了一個僅僅使用 CSS 實現的自由拖拽的便籤貼。我們可以自由的將其拖拽到任意地方。看看效果:

當然,我們可以再配合上另外一個有意思是 HTML 屬性 -- contenteditable

contenteditable 是一個 HTML TAG 的屬性,表示元素是否可被使用者編輯。如果可以,瀏覽器會修改元素的部件以允許編輯。

簡單修改一下 DOM 結構:

<div class="g-container">
    <div class="g-resize"></div>
    <div class="g-content" contenteditable="true"> Lorem ipsum dolor sit amet consectetur?</div>
</div>

此時,元素不僅可以被拖動,甚至可以被重寫,感受一下:

純 CSS 實現的效果,非常的有意思,完整的程式碼,你可以戳這裡:Pure CSS Auto Drag Demo

最後

基於 resize 這個 CSS 屬性,其實還有很多有意思的用法。譬如我之前使用了 Resize 實現了一個圖片切換預覽的功能:CSS 奇思妙想 | 使用 resize 實現強大的圖片拖拽切換預覽功能 可以一併看看,相信能碰撞出更多火花。

感興趣的同學可以自己動手,更多的去嘗試,組合。

好了,本文到此結束,希望本文對你有所幫助