HEIF/HEIC深度解析:从Box结构到属性链的完整指南

发布时间:2026/10/17 0:17:53
HEIF/HEIC深度解析:从Box结构到属性链的完整指南 简介ISO/IEC 23008-12:2017 是国际标准《信息技术 异构环境中的高效编码和媒体交付 第12部分图像文件格式》的官方 PDF 原文对应 HEIF/HEIC 格式面向图像编解码、流媒体传输、跨平台格式兼容等方向的工程师、研究人员及标准合规测试人员。该标准结合 HEVC 编码定义了 HEIF/HEIC 文件的结构、元数据、图像序列、多图存储及派生图像等关键机制并涉及不同网络和设备环境下的互操作要求是理解和落地 HEIF/HEIC 格式的一手权威资料。压缩包内共 1 个文件为 PDF 格式大小约 906KB可直接离线翻阅标准全文包括范围、规范性引用文件、术语定义、总体要求、详细技术规格等章节。已有 259 人学习下载。读者可获得完整标准文本用于格式解析、编解码实现、技术预研或学术引用尤其适合需要深入掌握 ISO/IEC 23008-12:2017 规定细节的开发与研究人员。1. 图像落地的新容器HEIF 不只是“iPhone 那个格式”如果你处理过一批抓拍的连拍照片或者维护过一套面向多端投放的图片服务大概率已经撞上过 HEIF/HEIC 这两个后缀。ISO/IEC 23008-12:2017 就是定义这类文件格式的底层标准它全称是《异构环境下高效编码与媒体传输——第 12 部分图像文件格式》2017 年 12 月发布第一版从那时起 HEIF 就成了 HEVC 编码之外、容器层面的正式规范。它解决的并不是“压缩算法本身”而是把 HEVC 编码的静态图像、图像序列、派生图像和元数据组织进一个可解析、可扩展、可交换的文件结构里。这套格式最反直觉的地方在于它并不是 JPEG 那样“一图一码流”的简单封装而是借用了 ISO 基础媒体文件格式BMFF的 box 体系把图片参数、属性、缩略图、旋转裁剪、辅助图层和主码流分离存放。这意味着你拿到一个 .heic 文件不能只靠解码器读出像素就算完事还要正确解析属性链和派生关系否则就会出现“能解码、但方向错、裁切错、动态范围错”这类让人头皮发麻的怪问题。适合谁看如果你要写一个支持 HEIF 的转码服务、客户端解析器或者打算在公司内部推广 HEIF 替换 JPEG这篇文章能帮你避开第一轮踩坑。2. 文件内部长什么样box 体系与属性解析的完整链路2.1 从 BMFF 继承来的骨架ftyp、meta、mdat 三件套ISO/IEC 23008-12:2017 并没有另起炉灶它第 6.2 节明确写了“Derivation from the ISO base media file format”也就是从 BMFF 派生。一个 HEIF 文件最外层是一串嵌套的 box每 个 box 由 size4 字节、type4 字节和 payload 组成。最常见的顶层布局是ftyp、meta、mdat三个 box 的顺序ftyp声明品牌和兼容版本meta里存放所有图像项的索引、属性、派生关系mdat里才是真正的编码码流数据。拿一个典型的单图 HEIF 举例解析器的工作顺序是这样的$ xxd sample.heic | head -n 5 00000000: 0000 0020 6674 7970 6865 6963 0000 0000 ... ftyp heic.... 00000010: 6865 6963 6d69 6669 6865 6963 6d69 6666 heic mifi heic miff 00000020: 0000 0000 6d65 7461 0000 0000 0000 0024 .... meta.......$这段十六进制里能看到文件头 8 字节之后是ftyp品牌写的是heic后面跟着metabox它的内部子 box 数量很大但最核心的是iprpItem Properties Box和iinfItem Info Box。iinf里每一项是一条 ItemInfoEntry用来描述图像项的 ID、类型是hvc1还是grid、名称和保护信息iprp里的 ItemPropertiesBox 则把具体的属性编码类型、分辨率、颜色信息按 Item 关联起来。理解这个两层结构是后面排查一切问题的基础。常见的一个新手误区是以为mdat里的码流用 HEVC 解码器一解就能出图。实际上一张 4000x3000 的 HEIF 照片mdat里可能只存了一个 1920x1080 的主编码再用irot旋转和clap裁剪属性推导出最终输出尺寸。你不读属性直接解拿到的只是一张“原料图”和最终呈现的图差着十万八千里。2.2 ispe 与图像空间范围从解码尺寸到显示尺寸的换算ispeImage Spatial Extents属性是所有图像项必须带的它记录了图像项的宽度和高度单位是像素。这里有个容易忽略的细节ispe里写的并不一定是 HEVC 码流里 SPS 声明的那组分辨率而是“这个图像项在逻辑上占据的空间大小”。派生图像里这个差异尤其明显——比如用grid图像拼接类型把四张 1280x720 的图拼成一张 2560x1440 的全景图四路 HEVC 码流各自的分辨率仍是 1280x720但ispe属性写的是 2560x1440。解析代码里要区分这两个维度import struct def read_ispe(ipma_entry, box_payload): # ipma_entry 是 ItemPropertyAssociation 里的一项 # box_payload 是 ispe 属性 box 的完整内容 # ispe box 结构: version(1) flags(3) width(4) height(4) version box_payload[0] flags struct.unpack(I, box_payload[0:4])[0] 0xFFFFFF width struct.unpack(I, box_payload[4:8])[0] height struct.unpack(I, box_payload[8:12])[0] return width, height这段代码只解析属性 box 本身。参数说明version当前固定为 0flags一般也是 0但解析时不要跳过校验因为标准里预留了扩展位宽度和高度是 32 位无符号整数单位是像素不存在浮点比例。实际项目中我通常会再读一下 HEVC 的 SPS把码流真实分辨率和ispe做一次比对两者不一致时优先信ispe但打印警告日志。2.3 图像属性链属性怎么挂到图像项上属性本身放在ipcoItem Property Container里是一个平铺的属性盒子列表。真正把它们和图像项关联起来的是ipmaItem Property Associationbox。ipma的结构是一个 entry 数组每个 entry 记录了一个 item_ID 和一个关联属性索引列表。关联索引有正负之分正值指向ipco中从头数第 N 个属性负值表示该属性是“essential”即解析器必须理解这个属性否则不能渲染这张图。写解析器时最容易翻车的地方是一个图像项可能关联了多个ispe属性标准规定只有第一个生效其余忽略另一个典型坑是ipma的关联顺序并不保证和渲染顺序一致旋转、裁剪、镜像这些操作有固定的应用顺序。标准 6.5.10、6.5.11、6.5.12 规定的顺序是先裁剪clap再旋转irot再镜像imir最后才是像素纵横比pasp调整。如果解析器按属性在ipma里出现的先后做变换八成会得到一张方向错误或比例怪异的图。正确的伪代码流程是这样# 伪代码属性应用顺序 1. 解析 ipma 拿到 item 的全部属性索引保持 container 内的声明顺序 2. 识别 essential 属性若遇到未知 essential 属性则直接报错 3. 按固定顺序应用变换clap - irot - imir - pasp 4. 用 ispe 给出的宽高作为输出画布尺寸这个顺序在标准里是强制的别试图“优化”它。我见过某个开源库里按属性在ipma里出现的先后做变换导致同一张 HEIF 图在不同版本库里出图方向不一致排查了两天才定位到是顺序写反了。从那以后我每次写 HEIF 解析器都会先画一张属性作用顺序图贴墙上。3. 从 HEVC 码流到可见图像解码与配置的配合方式3.1 hvcC 配置与 Decoder ConfigurationHEIF 里的图像码流是裸的 HEVC 访问单元不包含 SPS/PPS/VPS。这些参数集被单独抽取出来放在hvcCHEVCConfigurationBox属性里通过ipma关联到对应的图像项。解码器必须先用hvcC里的参数集初始化才能解码mdat里的码流。hvcC的内部结构相当紧凑解析时要注意字节序和位域import struct def parse_hvcc(box_payload): # box_payload 是 hvcC 属性的完整内容 configurationVersion box_payload[0] general_profile_space (box_payload[1] 6) 0x03 general_tier_flag (box_payload[1] 5) 0x01 general_profile_idc box_payload[1] 0x1F general_profile_compatibility_flags struct.unpack(I, box_payload[2:6])[0] general_constraint_indicator_flags box_payload[6:12] general_level_idc box_payload[12] # 读取 numOfArrays这个字段决定后面有几个参数集数组 numOfArrays box_payload[13] offset 14 sps_list [] pps_list [] vps_list [] for i in range(numOfArrays): array_completeness (box_payload[offset] 7) 0x01 nal_unit_type box_payload[offset] 0x3F numNalus struct.unpack(H, box_payload[offset1:offset3])[0] offset 3 for j in range(numNalus): nal_length struct.unpack(H, box_payload[offset:offset2])[0] offset 2 nal_data box_payload[offset:offsetnal_length] offset nal_length if nal_unit_type 33: # SPS sps_list.append(nal_data) elif nal_unit_type 34: # PPS pps_list.append(nal_data) elif nal_unit_type 32: # VPS vps_list.append(nal_data) return { profile_idc: general_profile_idc, level_idc: general_level_idc, sps: sps_list, pps: pps_list, vps: vps_list, }这段代码把hvcC里的所有参数集抽出来。参数说明nal_length是每个参数集前固定的 2 字节长度前缀不是 4 字节nal_unit_type判断用 0x3F是因为第 7 位被array_completeness占用第 6 位是保留位。最常见的兼容性问题出在general_profile_space和general_tier_flag上这两个字段组合决定了 Main 和 High 档次解码端不支持时表现为“花屏 解码失败”而不是直接提示不兼容。3.2 单图、图像集合与派生图的三种读取路径一 个 HEIF 文件可以同时包含多张图它们分成三类独立编码图像hvc1、派生图像iden、grid、iovl、以及隐藏图像。读取时要先区分这三条路径因为处理逻辑完全不同。独立编码图像最简单hvcC初始化解码器从mdat里按 item_ID 找到对应码流数据块解码即得完整图像。派生图像里的grid类型则要麻烦得多——它把一张逻辑大图切分成多块子图分别编码解码时需要先解码所有子图再按指定的行数、列数拼回完整图。标准 6.6.2 里grid的属性结构里有个rows和columns字段分别表示网格的行数和列数。处理隐藏图像时有一个非常容易忽略的坑隐藏图像hidden images不会出现在iinf的主列表里它们通过hidd属性标记。很多解析器只遍历iinf结果漏掉了主图上附带的深度图、Alpha 图、或者 HDR 增益图。正确做法是先遍历ipma拿到所有出现过的 item_ID再回头去iinf里查这些 item 的具体类型。3.3 Cover Image 语义没有主图时怎么选展示图标准里定义了 Cover Image封面图的角色它是“在支持单图渲染但不支持多图渲染的环境下应该默认展示的那一张”。如果文件里没有显式标记 Cover Image默认取iinf中第一个非隐藏、非辅助的hvc1项。我在做缩略图服务时踩过这个坑某批 HEIF 文件里作者把主图画质设得很低另存了一张高质量补光图作为 Auxiliary Image辅助图但没设置任何 Cover Image 标记。缩略图服务按“第一个 hvc1”原则取到了低画质那张线上展示效果惨不忍睹。排查时把ipma里的辅助图关联关系翻出来才发现是语义理解错了。从那以后我解析 HEIF 时一定先完整遍历ipma再决定哪张是“应该展示的图”绝不只看iinf的第一项。4. 动手实践搭一个可用的 HEIF 解析与转码管线4.1 工具链选型解析库与转码器的边界做 HEIF 工具链第一件事是搞清楚库的职责边界。libheif 负责容器解析和属性读取但不负责 HEVC 解码——它依赖外部解码器x265 编码、libde265 或 x265 解码而 Mac 上的 ImageIO 是系统级封装把容器解析和硬件解码绑在了一起跨平台时行为不一致。我的经验是服务端用 libheif libde265 的组合可控性最强客户端临时看一眼则用系统自带的解码器别重复造轮子。4.2 从 HEIF 提取元数据与缩略图的 Python 代码下面这段代码是提取 HEIF 文件元数据并生成缩略图的完整流程我在转码服务里跑过很多次import struct from PIL import Image import io import subprocess import tempfile import os def heif_to_jpeg_thumbnail(input_path, output_path, target_width256): # 先用 heif-info 查看文件结构调试用可去掉 subprocess.run([heif-info, input_path], checkFalse) # 用 heif-convert 或自研解析器拿到主图 with tempfile.TemporaryDirectory() as tmpdir: base_name os.path.splitext(os.path.basename(input_path))[0] # --with-aux 选项会导出所有辅助图默认只导主图 subprocess.run([ heif-convert, input_path, os.path.join(tmpdir, base_name .jpg), --with-aux ], checkTrue) # 找到所有导出文件选尺寸最大的一张作为主图 files os.listdir(tmpdir) if not files: raise RuntimeError(heif-convert 未输出任何图像) main_image_path None main_size 0 for f in files: fp os.path.join(tmpdir, f) img Image.open(fp) w, h img.size if w * h main_size: main_size w * h main_image_path fp if main_image_path is None: raise RuntimeError(未找到主图) img Image.open(main_image_path) # 保持宽高比缩放居中裁剪到目标宽度 ratio target_width / img.width new_height int(img.height * ratio) img img.resize((target_width, new_height), Image.LANCZOS) img.save(output_path, JPEG, quality85) print(f缩略图已生成: {output_path}, 尺寸: {img.size[0]}x{img.size[1]}) heif_to_jpeg_thumbnail(input.heic, output.jpg, target_width256)这段代码的逻辑分三层最外层用heif-convert先把 HEIF 转成 JPEG避免直接在 Python 层处理复杂 box然后把输出文件全部加载进来用像素面积判断哪张是主图最后按目标宽度做等比缩放。参数说明--with-aux会把 Alpha 图、深度图等辅助图也导出来如果你确认文件里没有辅助图可以去掉这个参数减少 IO缩放插值用LANCZOS比默认的BICUBIC更锐利适合做缩略图。4.3 从 HEIF 提取元数据与缩略图的 PowerShell 脚本Windows 场景服务端跑在 Windows 上的时候可以用系统自带的 Windows Imaging ComponentWIC来做不需要装额外运行时。下面这个 PowerShell 脚本可以批量提取一批 HEIC 文件的方向和尺寸并输出 CSV 报告$folderPath D:\temp_heic $outputCsv D:\temp_heic\report.csv $results () $files Get-ChildItem -Path $folderPath -Filter *.heic -Recurse foreach ($file in $files) { try { $image [System.Drawing.Image]::FromFile($file.FullName) $width $image.Width $height $image.Height $orientation $image.PropertyIdList -contains 0x0112 ? $image.GetPropertyItem(0x0112).Value[0] : N/A $results [PSCustomObject]{ FileName $file.Name Width $width Height $height OrientationTag $orientation } $image.Dispose() } catch { $results [PSCustomObject]{ FileName $file.Name Width ERROR Height ERROR OrientationTag $_.Exception.Message } } } $results | Export-Csv -Path $outputCsv -NoTypeInformation Write-Host 报告已生成: $outputCsv这个脚本的要点在于WIC 的FromFile会自动读取容器里的旋转属性所以Width和Height拿到的是“显示尺寸”而不仅是编码尺寸OrientationTag需要检查属性是否存在因为不是所有 HEIF 文件都会写 EXIF Orientation。用这个脚本先摸清你的图片集里有多少张带有 Orientation 标记、有多少张是横图竖图再决定转码服务需不需要做自动旋转。某次整理素材时用这个脚本一跑发现一个子目录里的图超过一半都带旋转标记而之前的转码服务完全忽略了它——难怪线上图片常常横竖不对。4.4 常见问题方向、裁剪元数据与解码器配置转向是 HEIF 最常见的“第一坑”。绝大多数手机拍的 .heic 文件实际编码时已经按传感器方向把图像转正了irot属性只用来标记“如果我要原原本本按采集方向显示需要旋转多少度”。但有些相机厂商的 HEIF 文件不会先转正而是依赖irot做显示旋转。两种策略混在一个图库里时最稳妥的办法是解码后总是检查ipma里是否有irot属性有就按角度旋转没有就原样输出。千万别用 EXIF 里的 Orientation 字段替代——HEIF 标准明确要求解析器用irot而不用 EXIF Orientation两套体系并存时以irot为准。裁剪clap则更隐蔽。clap属性定义的是清理孔径它会裁剪掉编码图像的边缘像素通常是为了去掉编码填充产生的黑边或畸变。当clap和irot同时存在时必须严格按裁剪 → 旋转的顺序处理。某次线上图片服务出现“某批图比原图小了一圈”的投诉最后定位到是解析器先旋转再裁剪旋转后的图像尺寸变了裁剪坐标对不上白边被裁掉一部分。这是标准里明文规定的顺序违反它不会报错只会给你一张不对的图。5. 避坑指南HEIF/HEIC 解析的五个真实教训5.1 用iinf直接解码主图忽略ipma属性链现象解码器能解出图但方向、裁剪全不对某些图甚至出现绿边。原因iinf只声明了 item 的存在和类型ipma才负责把属性和 item 关联。只按iinf里的 item_ID 去mdat找码流等于把属性链丢掉了。解决强制所有解析流程先从ipma构建“item_ID → 属性列表”的映射表再决定解码策略。哪怕你的使用场景里文件都是库自己生成的也要走完整属性解析因为总会有外部文件混进来。5.2 把hvcC当普通 box 跳过导致解码器初始化失败现象解码器报 no SPS/PPS或者视频画面频繁出现马赛克。原因有些简化解析器只读了mdat里的码流忽略了hvcC。HEVC 的 SPS/PPS/VPS 就在hvcC里解码器没有参数集无法初始化。解决解析hvcC时先读configurationVersionhardcode 为 1再把numOfArrays字段当成数组长度逐个读入 SPS/PPS/VPS。每个 NAL 前面是 2 字节长度不是 4 字节别搞混。一个可靠的验证方法是解码完成后对比ispe宽高和 SPS 里的分辨率不一致时立刻打日志。5.3 用 EXIF Orientation 替代irot做旋转现象同一张图在浏览器、手机相册、自研转码服务里方向不一致。原因HEIF 标准里的旋转语义由irot属性承担EXIF Orientation 是历史遗留。iPhone 生成的 HEIF 文件里两个字段可能同时存在但值不一定一致。解决无条件信任irot。解析后在内部生成统一的旋转角度字段EXIF Orientation 只对 JPEG 输出做转换时使用。5.4 忽略隐藏图像Alpha 图、深度图直接丢失现象导出的 PNG 没有透明通道或者 HDR 增益图、景深数据丢失。原因隐藏图像在iinf中被标记为隐藏但它们是真实存在的图像项。解析器只遍历iinf时永远拿不到它们的 item_ID更别说从mdat里提取码流。解决遍历ipma拿到全部 item_ID 集合再去iinf中查这些 ID 的类型。需要导出所有内容时要区分隐藏图像和辅助图像隐藏图通常在iinf里有记录但被hidd属性标记辅助图则通过auxC关联到主图。5.5 源文件里混着旧版heif品牌按新版规范解析失败现象某些 HEIF 文件在旧库里正常换到新库后渲染花屏。原因2017 版标准是 HEIF 第一版但 2018 年后有若干修订比如增加sato等新属性源文件用的规范版本和当前解析库不一致。品牌字段ftyp里写的是heic、mif1或heix不同的品牌暗示了不同的属性兼容范围。解决解析ftyp里的兼容品牌列表遇到不在白名单里的品牌时走兼容模式优先用 BMFF 基础属性和通用属性渲染遇到不认识的 essential 属性直接拒绝渲染并提示升级解析库。兼容模式比 strict 模式更适合线上服务——宁可多渲染一张也别因为某个新属性直接挂掉。6. 进阶验证技巧写一个真实的 HEIF 合规性检查脚本大量“解析不出来”的问题其实不是解码器不行而是文件本身就不合规。与其抓头排查不如提前写一个合规性检查器在文件进入管线的第一道关口就卡住异常格式。检查器的逻辑围绕三个层次顶层品牌检查、ipma属性链完整性检查、以及解码器与码流参数一致性检查。import struct import sys def check_heif_integrity(file_path): errors [] with open(file_path, rb) as f: data f.read() offset 0 brands set() meta_offset None mdat_offset None item_ids set() property_count 0 # 第一遍扫描顶层 box定位 meta 和 mdat while offset len(data): size struct.unpack(I, data[offset:offset4])[0] box_type data[offset4:offset8].decode(latin1) if size 1: size struct.unpack(Q, data[offset8:offset16])[0] if size 0: size len(data) - offset if box_type ftyp: major_brand data[offset8:offset12].decode(latin1) brands.add(major_brand) compatible_count struct.unpack(I, data[offset12:offset16])[0] for i in range(compatible_count): b data[offset16i*4:offset20i*4].decode(latin1) brands.add(b) elif box_type meta: meta_offset offset elif box_type mdat: mdat_offset offset offset size if meta_offset is None or mdat_offset is None: errors.append(缺少 meta 或 mdat box) return errors # 第二遍在 meta 里找 iprp meta_end meta_offset struct.unpack(I, data[meta_offset:meta_offset4])[0] offset meta_offset 8 # 跳过 box header while offset meta_end: size struct.unpack(I, data[offset:offset4])[0] box_type data[offset4:offset8].decode(latin1) if box_type iprp: iprp_end offset size ipco_end offset # 找 ipco 计算属性总数 sub_offset offset 8 while sub_offset iprp_end: sub_size struct.unpack(I, data[sub_offset:sub_offset4])[0] sub_type data[sub_offset4:sub_offset8].decode(latin1) if sub_type ipco: ipco_end sub_offset sub_size co_offset sub_offset 8 while co_offset ipco_end: property_count 1 co_size struct.unpack(I, data[co_offset:co_offset4])[0] co_offset co_size sub_offset sub_size offset size # 检查 meta 里是否至少有一个 iinf存在性检查 if biinf not in data[meta_offset:meta_end]: errors.append(meta 中缺少 iinf box) return errors errors check_heif_integrity(sys.argv[1]) if errors: print(合规性检查未通过) for e in errors: print(f - {e}) sys.exit(1) else: print(合规性检查通过)这个脚本的思路是分两遍扫描第一遍只找ftyp、meta、mdat三个顶层 box确认文件骨架存在第二遍深入meta检查iprp是否存在、ipco里的属性是否需要对应到ipma。参数说明ftyp里的 compatible_brand 列表读取的是 4 字节长度加若干 4 字节品牌名需要循环读取直到容器结束iprp内既有ipco属性容器也有ipma属性映射两个都存在才能保证属性链完整。一个注意点这个检查器只是做结构校验不负责解码。真正有经验的工程师会把三步检查做成一条命令链结构检查有没有 meta/mdat→ 属性检查ipma里 essential 属性是否都能识别→ 解码检查能不能用 libde265 解出像素。前两步通过只能说明容器没问题能不能出图还得看第三步。如果只管自家生成的文件可以在编码端加一道更狠的验证编码完成后立刻用独立解析器重新读一遍把解码出的图与原始图做像素级对比PSNR 或 SSIM。差异超过阈值就直接丢弃重编码。这个方法看起来“多耗一遍解码”但能拦住九成以上的参数配置错误——比如hvcC里nal_length_size写错导致解析歧义或者ispe与 SPS 分辨率不一致这类结构检查发现不了的问题。HEIF 这套规范真正考验人的地方在于它把“图像内容”和“图像语义”彻底分离了。JPEG 时代一个文件就是一整张图的像素而 HEIF 是容器思维——一张图是编码码流、属性链、派生关系组合出来的结果。这种设计的收益是极大的存储节省和编辑灵活性代价则是解析端永远不能偷懒跳过任何一层属性。做 HEIF 服务的这么长时间我的一个习惯收尾动作是无论改动多么微小都要跑一遍上面那个三步检查链把输出文件和原始文件做一次像素级对比。只有这步全过了才敢把文件传到线上。希望这篇拆解能帮你避开我当初踩过的坑在一个项目开始时就能把 HEIF 的底账摸清。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询