You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3中实现图片加载时显示Blurhash占位图的问题求助

Vue3 实现图片加载时显示 Blurhash 占位图的可行方案

核心思路

放弃已废弃的Vue2专用包,直接使用官方底层的blurhash库自定义Vue3组件渲染Blurhash,同时优化图片加载监听逻辑,避免布局跳动。

步骤1:安装依赖

安装官方维护的blurhash基础库:

npm install blurhash
# 或者
yarn add blurhash

步骤2:完善ItemBlurHashImg组件

这个组件负责将Blurhash哈希值渲染为模糊占位图,通过Canvas绘制实现:

<template>
  <canvas
    ref="canvasRef"
    :width="drawWidth"
    :height="drawHeight"
    class="blurhash-canvas"
  ></canvas>
</template>

<script setup>
import { ref, onMounted, computed } from 'vue';
import { decode } from 'blurhash';

const props = defineProps({
  hash: {
    type: String,
    required: true,
  },
  // 传入图片原始宽高比,保证模糊图比例和原图一致
  aspectRatio: {
    type: Number,
    default: 16 / 9,
  },
});

const canvasRef = ref(null);

// 用固定小尺寸解码Blurhash,既保证性能又足够清晰
const drawWidth = computed(() => 32);
const drawHeight = computed(() => drawWidth.value / props.aspectRatio);

onMounted(() => {
  if (!canvasRef.value || !props.hash) return;
  
  try {
    const pixels = decode(props.hash, drawWidth.value, drawHeight.value);
    const ctx = canvasRef.value.getContext('2d');
    const imageData = ctx.createImageData(drawWidth.value, drawHeight.value);
    imageData.data.set(pixels);
    ctx.putImageData(imageData, 0, 0);
  } catch (err) {
    console.error('Blurhash解码失败:', err);
  }
});
</script>

<style scoped>
.blurhash-canvas {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
</style>

步骤3:修改ItemCard组件的加载逻辑

改用叠加占位图的方式,通过透明度过渡实现平滑切换,同时直接监听真实图片的加载事件:

<template>
  <div class="card">
    <div class="card__item">
      <div class="card__image-wrapper">
        <!-- Blurhash占位图始终存在,图片加载完成后覆盖 -->
        <ItemBlurHashImg
          class="card__image placeholder"
          :hash="data.blurhash"
          :aspectRatio="15.625 / 15" <!-- 对应你的图片宽高比:width/height -->
        />
        <img
          class="card__image"
          :src="getSrc('.jpg')"
          :alt="data.alt"
          width="15.625rem"
          loading="lazy"
          @load="isLoaded = true"
          @error="isLoaded = true" <!-- 加载失败也隐藏占位图 -->
        />
      </div>
      <div class="card__content">
        <h2 class="card__title">{{ data.title }}</h2>
        <p class="card__text">{{ data.text }}</p>
        <router-link class="card__link" :to="{ name: 'Home' }">
          View more
        </router-link>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import ItemBlurHashImg from '@/components/SkeletonLoaders/ItemBlurHashImg.vue';

const props = defineProps(['data']);

const isLoaded = ref(false);

const getSrc = ext => {
  return new URL(
    `../assets/images/recipe/${props.data.image}${ext}`,
    import.meta.url
  ).href;
};
</script>

<style lang="scss">
.card {
  display: flex;
  justify-content: center;
  margin-top: 45px;
  align-items: center;
  &__item {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    color: $c-black;
    border-radius: $br-rounded;
    max-width: 22.5625rem;
    height: 30.25rem;
    background-color: $c-white;
  }
  &__image-wrapper {
    position: relative;
    height: 15rem;
    border-top-left-radius: $br-rounded;
    border-top-right-radius: $br-rounded;
    overflow: hidden;
  }
  &__image {
    position: absolute;
    top: 0;
    left: 0;
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: opacity 0.3s ease;
    opacity: v-bind(isLoaded ? 1 : 0);
  }
  &__image.placeholder {
    position: static;
    opacity: 1;
  }
}
</style>

关键优化点

  • 用绝对定位叠加占位图和真实图片,通过opacity过渡实现平滑切换,避免布局跳动
  • 直接监听img的@load和@error事件,比在onMounted中预加载更可靠,适配loading="lazy"场景
  • 传入图片真实宽高比,保证模糊占位图的比例与原图一致
  • 增加解码错误处理,避免无效哈希导致组件崩溃

内容的提问来源于stack exchange,提问作者Michael

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 14:07:08