从零开始-文件资源管理器-10-获取图片 exif 信息

使用 exifreader 依赖读取图片 Exif 信息。可读取常规 jpeg、png、WebP 等格式。数码相机的 RAW、CR2 均可读取。

可在本地浏览器直接运行,不需要上传至服务器进行解析。

开发

安装依赖

css 复制代码
pnpm i exifreader b64-to-blob

用于将 RAW、CR2 Exif 内 Thumbnail 字段转成 blob 后插入 img 标签内。

文件树

css 复制代码
src/app/path/[[...path]]/card-display.tsx
src/app/path/context.tsx
src/components/action-dropdown.tsx
src/components/img-exif-modal/img-exif-context.tsx
src/components/img-exif-modal/modal.tsx

文件路径:src/components/img-exif-modal/img-exif-context.tsx

创建一个显示 exif 上下文文件。用于控制需要获取 exif 文件的地址。

typescript 复制代码
'use client'
import createCtx from '@/lib/create-ctx'
import React from 'react'
import ImgExifModal from '@/components/img-exif-modal/modal'

export const ImgExifContext = createCtx<string>()
export const ImgExifProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
  return (
    <ImgExifContext.ContextProvider value={''}>
      {children}
      <ImgExifModal />
    </ImgExifContext.ContextProvider>
  )
}

文件路径:src/components/img-exif-modal/modal.tsx

exif 信息弹窗文件

  1. 使用 ExifReader.load 方法加载远端图片地址。解析获得 exif 信息后,写入当前弹窗状态,并显示对应的信息。当没有信息时显示 No Exif data
  2. 当 exif 信息内有 Thumbnail 字段时,使用 b64-to-blob 将 base64 转换成 blob,createObjectURL 一个 img 可使用的地址。
typescript 复制代码
'use client'
import React, { useState } from 'react'
import { Card, Descriptions, Drawer } from 'antd'
import { map, isEmpty, isUndefined, isNull } from 'lodash'
import { ImgExifContext } from '@/components/img-exif-modal/img-exif-context'
import ExifReader, { ThumbnailTags } from 'exifreader'
import { useMount } from 'ahooks'
import b64toBlob from 'b64-to-blob'

const filter_map = ['ApplicationNotes']

const Thumbnail: React.FC<{ item?: ThumbnailTags }> = ({ item }) => {
  return (
    item && (
      <picture>
        <img src={URL.createObjectURL(b64toBlob(item.base64, item.type))} alt="" />
      </picture>
    )
  )
}

const ExifItem: React.FC = () => {
  const img_path = ImgExifContext.useStore()
  const [info, changeInfo] = useState<ExifReader.Tags | null>()

  useMount(() => {
    ExifReader.load(window.location.origin + img_path)
      .then((data) => {
        changeInfo(data)
      })
      .catch(() => {
        changeInfo(null)
      })
  })

  return (
    <Card loading={isUndefined(info)}>
      {isNull(info) ? (
        'No Exif data'
      ) : (
        <Descriptions column={1} bordered={true} labelStyle={{ width: '20em', textAlign: 'right' }}>
          {map(info, (item, key) =>
            filter_map.includes(key) ? null : (
              <Descriptions.Item key={key} label={key}>
                {key === 'Thumbnail' ? <Thumbnail item={info?.Thumbnail} /> : item.description}
              </Descriptions.Item>
            ),
          )}
        </Descriptions>
      )}
    </Card>
  )
}

const ImgExifModal: React.FC = () => {
  const img_path = ImgExifContext.useStore()
  const dispatch = ImgExifContext.useDispatch()

  return (
    <Drawer
      title="图片信息"
      placement="right"
      open={!isEmpty(img_path)}
      width={1000}
      onClose={() => dispatch('')}
      footer={false}
      destroyOnClose={true}
    >
      <ExifItem />
    </Drawer>
  )
}

export default ImgExifModal

文件路径:src/components/action-dropdown.tsx

弹出下拉菜单,添加一个显示属性菜单项

javascript 复制代码
'use client'
import React from 'react'
import { Dropdown } from 'antd'
import { ReaddirItemType } from '@/explorer-manager/src/type'
import { InfoOutlined } from '@ant-design/icons'
import { MenuProps } from 'antd/es/menu'
import { ImgExifContext } from '@/components/img-exif-modal/img-exif-context'
import { useReplacePathname } from '@/components/use-replace-pathname'
import { isGif, isImage, isRaw } from '@/components/preview/ext-rxp'

const ActionDropdown: React.FC<React.PropsWithChildren & { item: ReaddirItemType }> = ({ children, item }) => {
  const name = item.name
  const { staticPath } = useReplacePathname()
  const changeImgExif = ImgExifContext.useDispatch()
  const is_show_img_exif = isImage(name) || isRaw(name)
  const preview_path = staticPath(name)

  const menu: MenuProps = {
    items: [],
  }

  if (is_show_img_exif) {
    menu.items?.push({
      icon: <InfoOutlined />,
      label: '信息',
      key: 'info',
      onClick: () => {
        changeImgExif(preview_path)
      },
    })
  }

  return (
    <Dropdown menu={menu} trigger={['click']} destroyPopupOnHide={true}>
      {children}
    </Dropdown>
  )
}

export default ActionDropdown

文件路径:src/app/path/[[...path]]/card-display.tsx

将下拉菜单加入卡片 item 的 extra 位置

javascript 复制代码
...
import ActionDropdown from '@/components/action-dropdown'
import { EllipsisOutlined } from '@ant-design/icons'
...
            <Card
              title={item.name}
              extra={
                <ActionDropdown item={item}>
                  <Button icon={<EllipsisOutlined />} />
                </ActionDropdown>
              }
            >
...

文件路径:src/app/path/context.tsx

将 ImgExifProvider 组件插入,完成显示图片 exif 信息功能

javascript 复制代码
...
import { ImgExifProvider } from '@/components/img-exif-modal/img-exif-context'

export const PathContextProvider: React.FC<React.ProviderProps<ReaddirListType>> = ({ value, children }) => {
  return (
    <>
...
              <ImgExifProvider>{children}</ImgExifProvider>
...
    </>
  )
}

效果

AI 生成的图片 parameters 信息也在内

git-repo

yangWs29/share-explorer

相关推荐
也无晴也无风雨35 分钟前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
小牛itbull3 小时前
ReactPress:构建高效、灵活、可扩展的开源发布平台
react.js·开源·reactpress
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤5 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js