javascript基础之购物车主图放大镜功能

408 阅读4分钟

首先在淘宝上找一个宝贝链接,打开控制台,看一下淘宝上的放大镜功能的结构是怎样实现的

image.png

不难看出来,主图区域功能最外面一层是div用来存放主要功能区的,position: relative;,主图并没有定位,只是一个img标签,滑块区域和放大图区域都是相对于外侧父元素定位,附图利用background-position来改变位置,其次主图和放大图下载下来后,尺寸属性是一样的,都是750 * 1000,那么首先在html中搭建这样的一个结构。

html结构

在vscode中输入main>(div.product-img>img+div.zoom+div.tools)+div.product-info回车,则会生成上述的结构,手动添加一个图片链接,为了防止之后链接失效,

 <main>
    <div class="product-img">
        <img src="" alt="商品主图">
        <div class="zoom"></div>
        <div class="tools"></div>
    </div>
    <div class="product-info"></div>
</main>

样式

根据结构,写less设置一下各个dom节点的样式

main{
    display:flex;
    height: 450px;
    margin-right: 18px;
    width: 100%;
    .product-img {
        position: relative;
        height: 100%;
        width: 450px;
        background-color: #efefef;
        img {
            display:block;
            height: 100%;
            // 按照父元素尺寸等比缩放
            width: calc((450 / 1000) * 750px);
            margin: auto;
        }
        .zoom {
            position: absolute;
            top: 0;
            right: -20px;
            transform: translateX(100%);
            background-image: url(../assets/image/product.png);
            width: 388px;
            height: 100%;
            // 固定图片值,可动态获取
            background-size: 750px 1000px;
            border-radius: 10px;
            overflow: hidden;
            z-index: 2;
        }
        .tools {
            width: 128px;
            height: 128px;
            background: rgba(129, 167, 249, 0.485);
            position: absolute;
            left: 0;
            top: 0;
            z-index: 2;
        }
    }
    .product-info {
        flex: 4;
        background: #9f9f9f;
    }

}

加入样式的布局

image.png

放大逻辑

.tools滑块移动

分析一下滑块移动的范围,超过主图的范围禁止超越,超过主图外侧dom节点的位置滑块隐藏,前期为了方便观察,先不做隐藏功能,只做移动和范围限制。

这四条蓝色的线,就是滑块可移动的最大范围

class Vector2 {
    x: number;
    y: number;
    constructor(x = 0, y = 0) {
        this.x = x;
        this.y = y
    }
    // 相加
    add(v2: Vector2) {
        return new Vector2(v2.x + this.x, v2.y + this.y)
    }
    // 相减
    sub(v2: Vector2) {
        return new Vector2(this.x - v2.x, this.y - v2.y)
    }
    divided(n: number) {
        return new Vector2(this.x / n, this.y / n)
    }
}

// 获取图片相关尺寸
const img = document.querySelector('img')
const width = img?.offsetWidth
const height = img?.offsetHeight
const imgSize = new Vector2(width, height)

// 图片左上角距离父元素的位置
const imgLeft = img?.offsetLeft
const imgTop = img?.offsetTop
const LT = new Vector2(imgLeft, imgTop)
// 图片右下角位置
const RB = LT.add(imgSize)

const imgParent = document.querySelector('.product-img') as any;
const parentPos = new Vector2(imgParent.offsetLeft, imgParent.offsetTop)

// zoom图层信息
const zoom = document.querySelector('.zoom') as HTMLDivElement;
const zoomSize = new Vector2(zoom.offsetWidth, zoom.offsetHeight)

// 移动模块相关参数
const tools = document.querySelector('.tools') as HTMLDivElement;
const toolsLeft = tools.offsetLeft;
const toolsTop = tools.offsetTop;

// 根据zoom图层跟大图尺寸的比例计算出tools的尺寸
// 犯懒了,750和1000这两个数值应该从图片上获取
const toolsWidth = zoomSize.x / 750 * imgSize.x
const toolsHeight = zoomSize.y / 1000 * imgSize.y
tools.style.width = toolsWidth + 'px'
tools.style.height = toolsHeight + 'px'
const toolsSize = new Vector2(toolsWidth, toolsHeight)

// 鼠标位置
const mouseMove = (e: any) => {
    // 获取当前鼠标位置
    const mouse = new Vector2(e?.clientX, e?.clientY);
    // console.dir(mouse);
    // 鼠标当前位置-父元素距离页面位置-模块尺寸的一半
    const toolsPosition = mouse.sub(parentPos).sub(toolsSize.divided(2))
    tools.style.left = toolsPosition.x + 'px'
    tools.style.top = toolsPosition.y + 'px'
}

if (imgParent) {
    imgParent.onmousemove = mouseMove
}


以上为移动模块的功能,效果如下

关于Vector2类是为了方便各个坐标之间的计算,不使用也可以,只要能计算出来就行,

限制范围

新增一个限制方法,传入当前模块位置,进行限制后,返回可在位置的值

// 图片右下角限制
const RBRange = RB.sub(toolsSize)
// 限制范围
const limitRange = (pos: Vector2): Vector2 => {
    let { x, y } = pos

    // 先判断左上角
    x = x >= LT.x ? x : LT.x
    y = y >= LT.y ? y : LT.y

    // 再判断右下角
    x = x <= RBRange.x ? x : RBRange.x
    y = y <= RBRange.y ? y : RBRange.y
    
    return new Vector2(x, y)
}

在mousemove方法中使用这个限制方法

...
const pos = limitRange(toolsPosition)
tools.style.left = pos.x + 'px'
tools.style.top = pos.y + 'px'
...

限制范围后的效果

放大zoom图

按照上图的比例计算公式,带入到getScale方法中,改变.zoom元素的backgroundPosition属性

const getScale = () => {
    // 改变左右位置
    const left = tools.offsetLeft - (img?.offsetLeft || 0)
    const scaleX = left / imgSize.x
    const x = scaleX * zoomBackgroundImageWidth
    zoom.style.backgroundPositionX = -x + 'px'

    // 改变上下位置
    const top = tools.offsetTop - (img?.offsetTop || 0)
    const scaleY = top / imgSize.y
    const y = scaleY * zoomBackgroundImageHeight
    zoom.style.backgroundPositionY = -y + 'px'
}

zoomBackgroundImageWidth 这个尺寸是大图的原本的尺寸,前面写的固定值,按理说这个值应该动态获取。

细节调整

接下来剩的工作就相对简单一点,根据不同时机,将元素显示隐藏即可

2023-06-09 10.06.51.gif

历史文章

# 写一个高德地图巡航功能的小DEMO

# Javascript基础之鼠标拖拉拽

# threejs 打造 world.ipanda.com 同款3D首页

# three.js——镜头跟踪

# threejs 笔记 03 —— 轨道控制器