【動(dòng)手系列】以鼠標(biāo)為中心對(duì)圖片進(jìn)行縮放

在上一家公司開(kāi)發(fā)的時(shí)候肤京,看到一個(gè)流程圖組件,里面有一個(gè)拖拽和縮放的功能,縮放很雞肋忘分,不會(huì)以鼠標(biāo)中心點(diǎn)縮放棋枕。所以用戶在縮放的時(shí)候,還得不停的拖拽妒峦。

以用戶體驗(yàn)為第一的原則重斑,我就想著把這個(gè)功能的體驗(yàn)弄好一點(diǎn),在網(wǎng)上找了一些資料:

最后選擇的是 css3 實(shí)現(xiàn)窥浪,效果圖:

image

思路

最開(kāi)始界面應(yīng)該是有一個(gè) div(400 * 300),如下:

初始化div

然后假設(shè)用戶進(jìn)行鼠標(biāo)放大之后笛丙,scale是 1.4:

圖片放大

這個(gè)時(shí)候漾脂,transform的值應(yīng)該是translate(-80px, -60px) scale(1.4)

計(jì)算過(guò)程:(scale后的高度 - 最開(kāi)始的高度) * 鼠標(biāo)在圖片高度位置的比例胚鸯。

  1. 圖片高度是 300px骨稿,假設(shè)鼠標(biāo)在 150px 的位置,得到位置比例是 150 / 300 = 0.5姜钳,放大后的高度是 300 * 1.4 = 420px坦冠,向上增加的高度應(yīng)該是 (420 - 300) * 0.5 = 60px。
  2. 不管是縮小還是放大哥桥,都把上一次translate對(duì)應(yīng)坐標(biāo)的值 - 這次得到的值辙浑,最后得出 translate 屬性上y的值是上一次的值(0) - 60 = -60

鼠標(biāo)在圖片上的比例,也就是 150px 是如何來(lái)的拟糕,以 y 軸為例:鼠標(biāo)的位置(event.y) - 縮放元素的父元素距離屏幕頂部的距離(通過(guò)dom.getBoundingClientRect().top可以獲取到)

代碼如下:

/**
 * 元素縮放判呕、拖拽
 * @param {string | HTMLBaseElement} selector 元素選擇器或者一個(gè)元素
 * @param {number} [scale] 初始化的縮放比
 * @param {object} [option] 其他選項(xiàng)
 * @param {number} [option.interval = 0.1] 每次疊加的間隔數(shù)
 * @param {number} [option.minScale = 0.5] 最小縮放
 * @param {number} [option.maxScale = 3] 最大縮放
 * @param {number} [option.disabledZoom = false] 是否禁用縮放,默認(rèn) 否
 * @param {number} [option.disabledDrag = false] 是否禁用拖拽已卸,默認(rèn) 否
 * @param {number} [option.slopOver = true] 是否可以超出父容器邊界佛玄,默認(rèn) 是
 */
function zoom (selector, scale = 1, option = {}) {
    // 記錄 Translate 的坐標(biāo)值
    let prevTranslateMap = {
        x: 0,
        y: 0
    }
    let zoomDom = selector,
        mx, // 記錄鼠標(biāo)按下時(shí)的 x 坐標(biāo)
        my, // 記錄鼠標(biāo)按下時(shí)的 y 坐標(biāo)
        tLeft = prevTranslateMap.x, // 最后設(shè)置的 translateX 值
        tTop = prevTranslateMap.y, // 最后設(shè)置的 translateY 值
        newsetWidth, // 拖動(dòng)容器最新的寬度
        newsetHeight, // 拖動(dòng)容器最新的高度
        firstMoveFlag = false // 第一次移動(dòng)標(biāo)記,防止用戶第一次按下和松開(kāi)鼠標(biāo)但并未移動(dòng)累澡,第二次移動(dòng)時(shí) dom 出現(xiàn)閃現(xiàn)的情況
    const { interval = 0.1, minScale = 0.5, maxScale = 3, slopOver = true, disabledZoom = false, disabledDrag = false } = option
    if (typeof selector === 'string') {
        zoomDom = document.querySelector(selector)
    }
    zoomDom.style.transformOrigin = '0 0';
    // 獲取最初始的寬高
    const { width: initWidth, height: initHeight } = zoomDom.getBoundingClientRect()
    const pDom = zoomDom.parentElement;
    // 滾動(dòng)事件兼容文章(https://www.zhangxinxu.com/wordpress/2013/04/js-mousewheel-dommousescroll-event/)
    !disabledZoom && zoomDom.addEventListener('mousewheel', ev => {
        const isZoomOut = ev.deltaY < 0; // 縮小
        // 鼠標(biāo)坐標(biāo)
        const { x: mouseX, y: mouseY } = ev;
        // 元素當(dāng)前寬高
        const { height, width } = zoomDom.getBoundingClientRect();
        const { top: pTop, left: pLeft } = pDom.getBoundingClientRect()
        if (isZoomOut) {
            // 縮小
            scale -= interval;
            if (minScale && scale < minScale) {
                scale = minScale
            }
        } else {
            // 放大
            scale += interval;
            if (maxScale && scale > maxScale) {
                scale = maxScale
            }
        }
        // 獲取比例
        let yScale = (mouseY - pTop - prevTranslateMap.y) / height;
        let xScale = (mouseX - pLeft - prevTranslateMap.x) / width;
        // 放大后的寬高
        const ampWidth = initWidth * scale
        const ampHeight = initHeight * scale
        // 需要重新運(yùn)算的 translate 坐標(biāo)
        const y = yScale * (ampHeight - height)
        const x = xScale * (ampWidth - width)
        // 更新
        const translateY = prevTranslateMap.y - y
        const translateX = prevTranslateMap.x - x
        zoomDom.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
        // 記錄這次的值
        prevTranslateMap = {
            x: translateX,
            y: translateY
        }
        ev.preventDefault()
    })
    // 鼠標(biāo)按下去
    !disabledDrag && zoomDom.addEventListener('mousedown', mousedown);
    
    function mousedown(ev) {
        mx = ev.x;
        my = ev.y;
        const clientRect = zoomDom.getBoundingClientRect()
        newsetWidth = clientRect.width
        newsetHeight = clientRect.height
        // 鼠標(biāo)移動(dòng)
        document.addEventListener('mousemove', mousemove);
        // 鼠標(biāo)松開(kāi)
        document.addEventListener('mouseup', mouseup);
    }
    function mousemove(ev) {
        firstMoveFlag = true
        tTop = prevTranslateMap.y + (ev.y - my)
        tLeft = prevTranslateMap.x + (ev.x - mx)
        if (!slopOver) {
            if (tTop < 0) tTop = 0
            if (tLeft < 0) tLeft = 0
            const rightBoundary = pDom.offsetWidth - newsetWidth // 右邊邊界
            const bottomBoundary = pDom.offsetHeight - newsetHeight // 下邊邊界
            if (tTop > bottomBoundary) tTop = bottomBoundary
            if (tLeft > rightBoundary) tLeft = rightBoundary
        }
        // 設(shè)置樣式
        zoomDom.style.cssText += `transform: translate(${tLeft}px, ${tTop}px) scale(${scale})`;
    }
    function mouseup() {
        if (firstMoveFlag) {
          prevTranslateMap = {
            x: tLeft,
            y: tTop
          }
        }
        document.removeEventListener('mousemove', mousemove);
        document.removeEventListener('mouseup', mouseup);
    }
}

zoom('#drag'); // <div><div id='drag'></div></div>

需要注意的幾行代碼梦抢,少了這幾行,縮放就達(dá)不到想要的效果:

  • zoomDom.style.transformOrigin = '0 0';要給縮放元素設(shè)置該屬性愧哟。
  • const { top: pTop, left: pLeft } = pDom.getBoundingClientRect();每次進(jìn)行縮放時(shí)獲取父元素的 topleft 值奥吩,用來(lái)獲取鼠標(biāo)坐標(biāo)在圖片比例最重要的一步。

代碼還有很多缺陷蕊梧,總會(huì)一步步完善的霞赫,努力吧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肥矢,一起剝皮案震驚了整個(gè)濱河市端衰,隨后出現(xiàn)的幾起案子叠洗,更是在濱河造成了極大的恐慌,老刑警劉巖旅东,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灭抑,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡抵代,警方通過(guò)查閱死者的電腦和手機(jī)腾节,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)荤牍,“玉大人案腺,你說(shuō)我怎么就攤上這事】党常” “怎么了劈榨?”我有些...
    開(kāi)封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)涎才。 經(jīng)常有香客問(wèn)我鞋既,道長(zhǎng),這世上最難降的妖魔是什么耍铜? 我笑而不...
    開(kāi)封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任邑闺,我火速辦了婚禮,結(jié)果婚禮上棕兼,老公的妹妹穿的比我還像新娘陡舅。我一直安慰自己,他們只是感情好伴挚,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布靶衍。 她就那樣靜靜地躺著,像睡著了一般茎芋。 火紅的嫁衣襯著肌膚如雪颅眶。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天田弥,我揣著相機(jī)與錄音涛酗,去河邊找鬼。 笑死偷厦,一個(gè)胖子當(dāng)著我的面吹牛商叹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播只泼,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼剖笙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了请唱?” 一聲冷哼從身側(cè)響起弥咪,我...
    開(kāi)封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤过蹂,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后酪夷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體榴啸,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡孽惰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年晚岭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勋功。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坦报,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出狂鞋,到底是詐尸還是另有隱情片择,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布骚揍,位于F島的核電站字管,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏信不。R本人自食惡果不足惜嘲叔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抽活。 院中可真熱鬧硫戈,春花似錦、人聲如沸下硕。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)梭姓。三九已至霜幼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間誉尖,已是汗流浹背罪既。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留释牺,地道東北人萝衩。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像没咙,于是被迫代替她去往敵國(guó)和親猩谊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 1祭刚、屬性選擇器:id選擇器 # 通過(guò)id 來(lái)選擇類名選擇器 . 通過(guò)類名來(lái)選擇屬性選擇器 ...
    Yuann閱讀 1,640評(píng)論 0 7
  • 在介紹有關(guān)transform相關(guān)的知識(shí)之前牌捷,先來(lái)講一下transform-origin的用法以及關(guān)于角度的幾種取值...
    跪鍵盤的小泰迪閱讀 1,247評(píng)論 0 2
  • CSS參考手冊(cè) 一墙牌、初識(shí)CSS3 1.1 CSS是什么 CSS3在CSS2.1的基礎(chǔ)上增加了很多強(qiáng)大的新功能。目前...
    沒(méi)汁帥閱讀 3,595評(píng)論 1 13
  • 一暗甥、CSS入門 1喜滨、css選擇器 選擇器的作用是“用于確定(選定)要進(jìn)行樣式設(shè)定的標(biāo)簽(元素)”。 有若干種形式的...
    寵辱不驚丶?xì)q月靜好閱讀 1,602評(píng)論 0 6
  • 看了很多視頻撤防、文章虽风,最后卻通通忘記了,別人的知識(shí)依舊是別人的寄月,自己卻什么都沒(méi)獲得辜膝。此系列文章旨在加深自己的印象,因...
    DCbryant閱讀 1,868評(píng)論 0 4