实现效果
期望功能
- 当悬浮框触碰到图片边界时,不会继续向外移动
- 悬浮放大展示的区域与悬浮框选的区域一致
思路
逻辑上分成三个部分
- 一部分是容器负责展示图片
- 还有一部分是悬浮框负责交互的悬浮效果
- 还有就是最终展示出来的图片
主要的交互是设置悬浮框的位置,和展示图片的区域
展示图片的区域位置放在哪里其实影响不大,悬浮框跟img就需要在一个container容器下,一个是为了img设置宽高100%撑满容器,一个是为了悬浮框设置absolute定位用于定位
可以得到html的结构大致如下
<div
ref="containerRef"
class="container"
:style="containerStyle"
>
<!-- img-box -->
<img :src="defaultImgSrc">
<!-- hover-box -->
<div class="hover-box" :style="hoverBoxStyle" />
</div>
<!-- preview-box -->
<div
class="preview-box" :style="previewBoxStyle"
/>
悬浮框的样式
悬浮框的基本样式如下
{
width: 100px,
aspect-ratio: 1,
position: absolute,
left: 0,
top: 0,
background-color: rgba(24, 144, 255,.6),
transform: `translateX(-50%)
translateY(-50%),
}
- 设置aspect-ratio而不是height是为了接下设置展示图片的大小时只需要设置一样的aspect-ratio就可以保证展示区域的比例同悬浮框的比例一致
- 接下来就是在transform上做功夫,获取到鼠标的位置,并对悬浮框做相应的偏移
获取鼠标的偏移位置
借助vueuse的useMouse,可以很轻松获取到鼠标的位置
const { x, y } = useMouse({
target: containerRef,
})
设置target为containerRef,这样x,y的值只会在鼠标在container容器上移动时才会更新
但这个位置相对于窗口的偏移量,而我们需要的是相对于container容器上的偏移量,所以还需要获取contain容器距离窗口的偏移量
通过不断叠加父容器的offsetLeft和offsetTop来做到
/**
* get offsetTop and offsetLeft to window
*/
function getPageLeftAndTop(element: HTMLElement) {
let pageLeft = element.offsetLeft
let pageTop = element.offsetTop
let parent = element.offsetParent as HTMLElement
while (parent) {
pageLeft += parent.offsetLeft
pageTop += parent.offsetTop
parent = parent.offsetParent as HTMLElement
}
return {
pageLeft,
pageTop,
}
}
const mousePosition = computed(() => {
let minusLeft = 0
let minusTop = 0
const containerElement = toValue(containerRef)
if (containerElement) {
const { pageLeft, pageTop } = getPageLeftAndTop(containerElement)
minusLeft = pageLeft
minusTop = pageTop
}
return {
mouseX: x.value - minusLeft,
mouseY: y.value - minusTop,
}
})
这样就在mousePosition里面得到了鼠标相对于container的x和y值
添加悬浮框的样式
此时,完整的悬浮框样式就可以写出来了
const hoverBoxStyle = computed<CSSProperties>(() => {
return {
width: `${toValue(hoverWidth)}px`,
aspectRatio: aspectRatio.value,
position: 'absolute',
left: 0,
top: 0,
backgroundColor: 'rgba(24, 144, 255,.6)',
transform: `translateX(-50%)
translateY(-50%)
translateX(${mousePosition.value.mouseX}px)
translateY(${mousePosition.value.mouseY}px)`,
}
})
这里把悬浮框的宽度(hoverWidth),和比例(aspectRatio)都抽离出来便于后续处理
处理鼠标移动到container边缘情况
当鼠标位置移动到container边缘时,悬浮框的位置也会跟随着移动到边缘,那显然我们是不希望悬浮框的边界超出容器的,这样就没有显示的图片了,所以需要再对mousePosition做一下处理
const mousePosition = computed(() => {
let minusLeft = 0
let minusTop = 0
let containerWidth = Infinity
let containerHeight = Infinity
const containerElement = toValue(containerRef)
if (containerElement) {
const { pageLeft, pageTop } = getPageLeftAndTop(containerElement)
minusLeft = pageLeft
minusTop = pageTop
containerWidth = containerElement.offsetWidth
containerHeight = containerElement.offsetHeight
}
return {
mouseX: Math.min(Math.max(toValue(hoverWidth as any) / 2, x.value - minusLeft), containerWidth - toValue(hoverWidth as any) / 2),
mouseY: Math.min(Math.max(toValue(hoverHeight as any) / 2, y.value - minusTop), containerHeight - toValue(hoverHeight as any) / 2),
}
})
使得mouseX最小为悬浮框宽度的一半,最大为容器的宽度减去一半的悬浮框的宽度,高度同理
这样就得到了一个悬浮框的样式,还有mousePosition用于后续的图片显示
图片展示框的样式
首先可以得到图片展示框的基本样式
{
width: 300px,
aspect-ratio: 1,
backgroundImage: url('example'),
overflow: 'hidden',
background-repeat: 'no-repeat',
}
计算背景图片的大小
我们可以根据展示容器的大小除以悬浮框的大小得到一个放大比例,再与展示框的大小做乘法,就可以得到图片的大小了
const previewBoxStyle = computed<CSSProperties>(() => {
const previewHeight = toValue(previewWidth as any) / aspectRatio.value
/** @default 3 */
const xRatio = toValue(previewWidth as any) / toValue(hoverWidth as any)
/** @default 3 */
const yRatio = previewHeight / toValue(hoverHeight as any)
/** @default 600 */
const xBgSize = toValue(containerWidth as any) * xRatio
/** @default 600 */
const yBgSize = toValue(containerHeight as any) * yRatio
return {
width: `${toValue(previewWidth)}px`,
aspectRatio: aspectRatio.value,
backgroundImage: `url(${imgSrc})`,
overflow: 'hidden',
backgroundRepeat: 'no-repeat',
backgroundSize: `${xBgSize}px ${yBgSize}px`,
}
})
计算背景图片的偏移量
以宽度为例,前面计算得到的mousePosition,是悬浮框使用的偏移量,而悬浮框的transform是做了translateX(-50%)和translateY(-50%)的,所以它的偏移中心可以视为悬浮框中心的位置,而图片展示框的偏移中心还是默认的左上角,所以需要先将mousePosition的值修正到左上角为中心的偏移量,也就是
mousePosition.value.mouseX - toValue(hoverWidth as any) / 2
将要偏移的值与之前的放大比例相乘,就可以得到图片的偏移量,不过图片的偏移是负的
就可以得到完整的展示图片框的样式
const previewBoxStyle = computed<CSSProperties>(() => {
const previewHeight = toValue(previewWidth as any) / aspectRatio.value
/** @default 3 */
const xRatio = toValue(previewWidth as any) / toValue(hoverWidth as any)
/** @default 3 */
const yRatio = previewHeight / toValue(hoverHeight as any)
/** @default 600 */
const xBgSize = toValue(containerWidth as any) * xRatio
/** @default 600 */
const yBgSize = toValue(containerHeight as any) * yRatio
const xPosition = (mousePosition.value.mouseX - toValue(hoverWidth as any) / 2) * -xRatio
const yPosition = (mousePosition.value.mouseY - toValue(hoverHeight as any) / 2) * -yRatio
return {
width: `${toValue(previewWidth)}px`,
aspectRatio: aspectRatio.value,
backgroundImage: `url(${imgSrc})`,
overflow: 'hidden',
backgroundRepeat: 'no-repeat',
backgroundSize: `${xBgSize}px ${yBgSize}px`,
backgroundPosition: `left ${xPosition}px top ${yPosition}px`,
}
})
其他样式
当鼠标不出现在container容器上时,我期望不展示悬浮框
.container{
margin: auto;
img{
width: 100%;
height: 100%;
// object-fit: contain;
}
.hover-box{
display: none;
}
&:hover .hover-box{
display: initial;
}
}
存在的问题
- 需要提前知道图片的大小,以便让图片盛满容器
- 无法给图片设置object-fit: cover;属性值来设置图片的显示效果,必须是撑满容器的