动态调整大小时如何使用 Sanity 图像热点

问题描述 投票:0回答:1

tl;dr 我想将 Sanity 热点保留在屏幕上 100vw 和 100dvh 部分,无论用户如何调整它的大小。

目标

在我客户的网站上,我有一个用于英雄部分背景的图像滑块。为了最大化地显示图像,该部分填充视口宽度和视口高度。我想确保热点在这个空间中始终可见,无论该部分有多大,因为它非常动态。

问题

无论我尝试什么,热点似乎都没有为我做任何事情。我尝试过的一些操作没有任何效果,而另一些则将图像移得太远,从而显示图像后面的空白。无论如何,即使是我最接近的尝试似乎也不包含热点。

代码

图像生成器

这只是 Sanity 常用的图像生成器助手

// /sanity.ts

import createImageUrlBuilder from '@sanity/image-url'

/**
 * ### imageUrlFor
 * - Gets the Sanity URL for images
 * - Has helper functions for customizing the output file
 *
 * @param source SanityImage
 * @returns url
 * @example imageUrlFor(image)?.url()
 * @example importUrlFor(image)?.format('webp').url()
 */
export const imageUrlFor = (source: SanityImageReference | AltImage) => {
  if (!config.dataset || !config.projectId)
    throw new Error('SANITY_DATASET or SANITY_PROJECT_ID is not set correctcly')

  // @ts-expect-error dataset and projectId have been verified
  return createImageUrlBuilder(config).image(source)
}

调整图像大小的辅助功能

(不重要;只是在下面的

sanity.ts
中使用)

// /utils/functions.ts

/**
 * ### Compress Width and Height
 * - Resize the width and height it's the ratio, to the max width/height in pixels
 * @param {number} width
 * @param {number} height
 * @param {number} max width/height (default: 25)
 * @returns {{ width: number, height: number }} { width: number, height: number }
 */
export function compressWidthAndHeight(
  width: number,
  height: number,
  max: number = 25,
): { width: number; height: number } {
  if (width === 0 || height === 0)
    throw new Error(
      'Cannot divide by zero. Please provide non-zero values for the width and height.',
    )

  if (width > height)
    return {
      width: max,
      height: Math.ceil((max * height) / width),
    }
  return {
    width: Math.ceil((max * width) / height),
    height: max,
  }
}

我为 Next.js Image 组件准备图像的函数

这是我希望能够以某种方式添加热点的地方,无论它是否在 imageUrlFor 中

// /utils/sanity.ts

import { SanityImageReference } from '@/typings/sanity'
import { getImageDimensions } from '@sanity/asset-utils'
import { ImageProps } from 'next/image'
import { compressWidthAndHeight } from './functions'
import { imageUrlFor } from '@/sanity'

/**
 * ### Prepare Image
 * @param {SanityImageReference & { alt: string }} image
 * @param {Partial<{ aspect: number, max: number, sizes: string, quality: number }>} options
 * @options aspect width/height
 * @options dpr pixel density (default: 2)
 * @options max width or height (default: 2560)
 * @options sizes
 * @options quality 1-100 (default: 75)
 * @returns {ImageProps}
 */
export function prepareImage(
  image: SanityImageReference & { alt: string },
  options?: Partial<{
    aspect: number
    dpr: number
    max: number
    sizes: string
    quality: number
  }>,
): ImageProps {
  const { alt } = image

  const aspect = options?.aspect,
    dpr = options?.dpr || 2,
    max = options?.max || 2560,
    sizes = options?.sizes,
    quality = options?.quality || 75

  if (aspect && aspect <= 0) throw new Error('aspect cannot be less than 1.')
  if (dpr <= 0) throw new Error('dpr cannot be less than 1.')
  if (dpr >= 4) throw new Error('dpr cannot be greater than 3.')
  if (max <= 0) throw new Error('max cannot be less than 1.')
  if (quality <= 0) throw new Error('quality cannot be less than 1.')
  if (quality >= 101) throw new Error('quality cannot be greater than 100.')

  let { width, height } = getImageDimensions(image)

  if (aspect) {
    const imageIsLandscape = width > height,
      aspectIsLandscape = aspect > 1,
      aspectIsSquare = aspect === 1

    if (aspectIsSquare) {
      if (imageIsLandscape) width = height
      if (!imageIsLandscape) height = width
    }

    if (aspectIsLandscape) {
      height = Math.round(width / aspect)
      width = Math.round(height * aspect)
    } else if (!aspectIsSquare) {
      height = Math.round(width / aspect)
      width = Math.round(height * aspect)
    }
  }

  // For the full image
  const { width: sizedWidth, height: sizedHeight } = compressWidthAndHeight(
    width,
    height,
    max,
  )

  // For the blurDataUrl
  const { width: compressedWidth, height: compressedHeight } =
    compressWidthAndHeight(width, height)

  const baseSrcUrl = imageUrlFor(image).format('webp').fit('crop')

  const src = baseSrcUrl
    .size(sizedWidth, sizedHeight)
    .quality(quality)
    .dpr(dpr)
    .url()

  const blurDataURL = baseSrcUrl
    .quality(15)
    .size(compressedWidth, compressedHeight)
    .dpr(1)
    .blur(200)
    .auto('format')
    .url()

  return {
    id: `${image.asset._ref}`,
    src,
    alt,
    width: sizedWidth,
    height: sizedHeight,
    sizes,
    quality: quality,
    placeholder: 'blur',
    blurDataURL,
  }
}

示例

const img = document.querySelector('#example-image')

const hotspot = {
  x: 0.804029,
  y: 0.239403,
  width: 0.154323,
  height: 0.154323
}

// This lines up the hotspot with the top left corner of the container—sort of…
// This is not the intended result, but it was my best attempt at least locating the hotspot in some way.
img.style.top = `${hotspot.y * 100}`
img.style.left = `${hotspot.x * 100}`
img.style.transform = `translateX(-${hotspot.x * 100}%) translateY(-${hotspot.y * 100}%) scaleX(${hotspot.width * 100 + 100}%) scaleY(${hotspot.height * 100 + 100}%)`
* {
  margin: 0;
  padding: 0;
  position: relative;
  min-width: 0;
}

html {
  color-scheme: light dark;
}

body {
  min-height: 100svh;
  font-family: ui-sans-serif;
}

img {
  font-style: italic;
  background-repeat: no-repeat;
  background-size: cover;
  shape-margin: 0.75rem;
  width: 100%;
  font-family: ui-serif;
}

h1 {
  font-weight: unset;
  font-size: unset;
}

.absolute {
  position: absolute;
}

.inset-0 {
  inset: 0;
}

.\-z-10 {
  z-index: -10;
}

.flex {
  display: flex;
}

.size-screen {
  width: 100vw;
  height: 100dvh;
}

.h-full {
  height: 100%
}

.items-center {
  align-items: center;
}

.justify-center {
  justify-content: center;
}

.mb-2 {
  margin-bottom: 0.5rem;
}

.ply-4 {
  padding-block: 1rem;
}

.plx-8 {
  padding-inline: 2rem;
}

.rounded-tl-10xl {
  border-top-left-radius: 4.5rem;
}

.rounded-tr-lg {
  border-top-right-radius: 0.5rem;
}

.rounded-bl-lg {
  border-bottom-left-radius: 0.5rem;
}

.rounded-br-10xl {
  border-bottom-right-radius: 4.5rem;
}

.bg-black\/60 {
  background-color: rgb(0 0 0 / 0.6)
}

.text-white {
  color: white;
}

.text-lg {
  font-size: 1.125rem;
}

.font-extralight {
  font-weight: 200;
}

.font-black {
  font-weight: 900;
}

.text-center {
  text-align: center;
}

.shadow-lg {
  box-shadow: 0 10px 20px 0 rgb(0 0 0 / 0.25);
}

.ring-2 {
  --ring-width: 2px;
}

.ring-inset {
  box-shadow: inset 0 0 0 var(--ring-width) var(--ring-color);
}

.ring-blue\/25 {
  --ring-color: rgb(0 0 255 / 0.25);
}

.object-cover {
  object-fit: cover;
}

.bg-blur-img {
  background-image: url(https://images.unsplash.com/photo-1711907392527-4a895b190b61?ixlib=rb-4.0.3&q=1&fm=webp&crop=entropy&cs=srgb&width=20&height=20);
}

.text-shadow {
  text-shadow: 0 2.5px 5px rgb(0 0 0 / 0.25);
}

.backdrop-brightness-150 {
  --backdrop-brightness: 1.5;
  -webkit-backdrop-filter: blur(var(--backdrop-blur)) brightness(var(--backdrop-brightness));
  backdrop-filter: blur(var(--backdrop-blur)) brightness(var(--backdrop-brightness));
}

.backdrop-blur-lg {
  --backdrop-blur: 16px;
  -webkit-backdrop-filter: blur(var(--backdrop-blur)) brightness(var(--backdrop-brightness));
  backdrop-filter: blur(var(--backdrop-blur)) brightness(var(--backdrop-brightness));
}
<section class='flex size-screen items-center justify-center ring-2 ring-inset ring-blue/25'>
  <div id='text-container' class='ply-4 plx-8 rounded-tl-10xl rounded-tr-lg rounded-bl-lg rounded-br-10xl bg-black/60 text-white text-center text-shadow shadow-lg backdrop-blur-lg backdrop-brightness-150'>
    <h1 class='text-lg font-black mb-2'>In this example, the sun is the hotspot.</h1>
    
    <p class='font-extralight'>Check JavaScript for explanation.</p>
  </div>

  <img
    id='example-image'
    src='https://images.unsplash.com/photo-1711907392527-4a895b190b61?ixlib=rb-4.0.3&q=75&fm=webp&crop=entropy&cs=srgb&width=2560&height=1707.046875'
    alt='Sunset, Canary Islands, Spain'
    width='2560'
    height='1707.046875'
    sizes='100vw'
    class='absolute inset-0 -z-10 h-full object-cover bg-blur-img'
    loading='eager'
  />
</section>

javascript css image next.js sanity
1个回答
0
投票

为了确保热点在动态大小的图像中保持清晰可见并正确对齐,特别是在像英雄滑块这样的完整视口宽度和高度部分中,您可以通过专注于图像定位和缩放的 CSS 和 JavaScript 处理来完善您的方法.

function adjustImagePosition(hotspot, imageElement) {
  const xPercent = hotspot.x * 100;
  const yPercent = hotspot.y * 100;

  // Adjust the object-position to focus on the hotspot
  imageElement.style.objectPosition = `${xPercent}% ${yPercent}%`;
}

document.addEventListener('DOMContentLoaded', () => {
  const img = document.querySelector('#example-image');
  const hotspot = {
    x: 0.804029, // Hotspot X coordinate as a fraction of width
    y: 0.239403  // Hotspot Y coordinate as a fraction of height
  };

  adjustImagePosition(hotspot, img);

  window.addEventListener('resize', () => {
    adjustImagePosition(hotspot, img);
  });
});
img {
  width: 110%; /* Slightly larger to ensure coverage */
  height: 110%;
  object-fit: cover;
  position: relative;
  left: -5%; /* Center the scaled image */
  top: -5%;
}
<section class='flex size-screen items-center justify-center ring-2 ring-inset ring-blue/25'>
  <div id='text-container' class='ply-4 plx-8 rounded-tl-10xl rounded-tr-lg rounded-bl-lg rounded-br-10xl bg-black/60 text-white text-center text-shadow shadow-lg backdrop-blur-lg backdrop-brightness-150'>
    <h1 class='text-lg font-black mb-2'>In this example, the sun is the hotspot.</h1>
    <p class='font-extralight'>Check JavaScript for explanation.</p>
  </div>

  <img
    id='example-image'
    src='https://images.unsplash.com/photo-1711907392527-4a895b190b61?ixlib=rb-4.0.3&q=75&fm=webp&crop=entropy&cs=srgb&width=2560&height=1707.046875'
    alt='Sunset, Canary Islands, Spain'
    class='absolute inset-0 -z-10 h-full object-cover bg-blur-img'
    loading='eager'
  />
</section>

解释👇

HTML 结构:除了确保图像占据整个部分并根据视口动态调整之外,结构基本保持不变。

CSS 更改:图像稍微放大,以确保调整对象位置以聚焦于热点时不会显示边缘周围的任何空白。

JavaScript 更改: adjustmentImagePosition 函数根据热点坐标动态设置对象位置。在页面加载时以及每次调整窗口大小时都会调用此函数,以确保无论屏幕大小如何,热点都保持可见并居中。

© www.soinside.com 2019 - 2024. All rights reserved.