linux-3.2.36内核启动2-setup_arch中的内存初始化1(arm平台 分析高端内存和初始化memblock)

本文详细解析了Linux内核启动过程中的内存初始化步骤,包括如何根据启动参数初始化内存信息、高端内存支持、内存块管理及内核与initrd内存区域的保留等关键环节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇微博留下了这几个函数,现在我们来分析它们

        sanity_check_meminfo();

        arm_memblock_init(&meminfo, mdesc);

        paging_init(mdesc);

        request_standard_resources(mdesc);

 

在上一微博有展现根据启动参数初始化meminfo,记录了物理内存的开始和大小

       sanity_check_meminfo();

mmu的情况下这个函数才有意义,初始化高端内存,首先内核要选上这个

KernelFeatures下的

[*]High Memory Support

arch/arm/include/asm/setup.h

#ifdef CONFIG_ARCH_EP93XX

# define NR_BANKS 16

#else

# define NR_BANKS 8 三星当然是8

#endif

 

struct membank {

        phys_addr_t start;

        unsigned longsize;

        unsigned inthighmem;

};如果是高端内存highmem为1

 

struct meminfo {

        int nr_banks;

        struct membankbank[NR_BANKS];

};

extern struct meminfo meminfo;

我们现在的函数就是初始化meminfo这个全局变量

高端内存

    linux内核的地址空间是3G~4G。假如说机器的内存为512M,那么内存的物

    理地址范围是:0~512,而映射到内核空间的范围是3G~3G+512M(可以叫low memory).

    而其余的空间都是高端内存的范围,即:3512G4G,但是为了避免越界等安全问题

    的考虑,高端内存又离开了低端内存8M空间,即从3G512M+8M空间开始。linux内核又规定,高端内存至少为128M,即加入物理内存为1G,那么高端内存就是从896M~4G,即其最大地址:0xC0000000+896M,实际:0xC0000000+x(内存size)

简单举个例子,假设你有2G内存,而内核只有1G不能全部做线性映射,内核就会把前896M用于RAM线性映射,后128M可以通过更改映射关系访问剩下的内存。有三种方法:永久内核映射,临时映射,非连续内存分配(这些以后写关于内存管理的文章时再分析)。

没有全部贴

void __init sanity_check_meminfo(void)

{

        int i, j, highmem= 0;

 

        //wxl add

        printk(KERN_NOTICE"vmalloc_min = %lx\n", vmalloc_min);

打印结果

vmalloc_min = ee000000

vmalloc_min = (void *)(VMALLOC_END - SZ_128M);

arch/arm/mach-s3c2410/include/mach/vmalloc.h
#define VMALLOC_END 0xF6000000UL

 

0xF6000000-0x8000000=0xEE000000

3808M

 

        for (i = 0, j = 0;i < meminfo.nr_banks; i++) {

                structmembank *bank = &meminfo.bank[j];

                *bank =meminfo.bank[i];

 

#ifdef CONFIG_HIGHMEM

                _va()是物理地址转换虚拟地址

#define __virt_to_phys(x)      ((x) - PAGE_OFFSET + PHYS_OFFSET)

#define __phys_to_virt(x)      ((x) - PHYS_OFFSET + PAGE_OFFSET)

#define __va(x)                ((void *)__phys_to_virt((unsigned long)(x)))

下面的条件告诉了我们高端地址范围,大于等于vmalloc_min的好理解,小于PAGE_OFFSET永久内核映射

                if(__va(bank->start) >= vmalloc_min ||

                   __va(bank->start) < (void *) PAGE_OFFSET)

                       highmem = 1;

                //wxl add

                printk(KERN_NOTICE "start:bank->start = %lx bank->size = %lx __va = %lx highmem = %d\n",(unsigned long)bank->start, (unsigned long)bank->size, (unsignedlong)__va(bank->start), highmem);

打印结果

start: bank->start = 30000000 bank->size = 4000000 __va =c0000000 highmem = 0

bank->start bank->size就是上一篇微博提到的

               bank->highmem = highmem;

 

                /*

                 * Splitthose memory banks which are partially overlapping

                 * thevmalloc area greatly simplifying things later.

                 */

                假设__va(bank->start) < vmalloc_min;它的大小可能会超过低端内存,也就是起始地址在低端,结束地址超过低端范围,那么就要把它分开,你可以简单看看代码

                if(__va(bank->start) < vmalloc_min &&

                   bank->size > vmalloc_min - __va(bank->start)) {

                        if(meminfo.nr_banks >= NR_BANKS) {

                               printk(KERN_CRIT "NR_BANKS too low, "

                                                 "ignoringhigh memory\n");

                        }else {

                               memmove(bank + 1, bank,

                                       (meminfo.nr_banks - i) * sizeof(*bank));

                               meminfo.nr_banks++;

                               i++;

                               bank[1].size -= vmalloc_min - __va(bank->start);

                               bank[1].start = __pa(vmalloc_min - 1) + 1;

                               bank[1].highmem = highmem = 1;

                               j++;

                        }

                       bank->size = vmalloc_min - __va(bank->start);

                }

                //wxl add

               printk(KERN_NOTICE "end: bank->start = %lx bank->size =%lx\n", (unsigned long)bank->start, (unsigned long)bank->size);

打印结果

end: bank->start = 30000000 bank->size = 4000000

 

#else

……

#endif

                重设低端内存限制

                if(!bank->highmem && bank->start + bank->size > lowmem_limit)

                        lowmem_limit =bank->start + bank->size;

 

                j++;

        }

……

 arm_memblock_init(&meminfo, mdesc);

        在此处按地址数据从小到大排序meminfo中的数据,并初始化全局的memblock数据。

void __init arm_memblock_init(struct meminfo *mi, structmachine_desc *mdesc)

{

        int i;

 

       sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]),meminfo_cmp, NULL);排序不细看了,而且我的就一个bank

 

        memblock_init();

这个就是对memblock变量初始化,该赋初值的符初值,该清零的清零。说一点

memblock里有个memorystruct memblock_type

 

struct memblock_region {

        phys_addr_t base;

        phys_addr_t size;

};

 

struct memblock_type {

        unsigned longcnt;      /* number of regions */

        unsigned longmax;      /* size of the allocated array*/

        structmemblock_region *regions;

};

 

初始化

static struct memblock_regionmemblock_memory_init_regions[INIT_MEMBLOCK_REGIONS + 1] __initdata_memblock;

static struct memblock_regionmemblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS + 1] __initdata_memblock;

memblock.memory.max = INIT_MEMBLOCK_REGIONS; 值为128

memblock.memory.cnt = 1;

memblock.memory.regions[0].base = 0;

memblock.memory.regions[0].size = 0;

memblock.memory.regions[INIT_MEMBLOCK_REGIONS].base =MEMBLOCK_INACTIVE;

MEMBLOCK_INACTIVE0x44c9e71bUL

 

memblock.current_limit = MEMBLOCK_ALLOC_ANYWHERE;

#define MEMBLOCK_ALLOC_ANYWHERE      (~(phys_addr_t)0)这样看你是多少位系统了,不过我们只要知道是anywhere

memblock还有个reserved,和memory初始化的值一样。

 

        for (i = 0; i <mi->nr_banks; i++)

               memblock_add(mi->bank[i].start, mi->bank[i].size);

long __init_memblock memblock_add(phys_addr_t base, phys_addr_tsize)

{

        return memblock_add_region(&memblock.memory,base, size);

我的base 0x30000000 size = 0x4000000;

}

 

static long __init_memblock memblock_add_region(structmemblock_type *type,

                                               phys_addr_t base, phys_addr_t size)

{

        phys_addr_t end =base + size;bank的结束地址

        int i, slot = -1;

 

        /* First try andcoalesce this MEMBLOCK with others */

        for (i = 0; i <type->cnt; i++) {

                structmemblock_region *rgn = &type->regions[i];

                phys_addr_t rend = rgn->base +rgn->size;

 

                /* Exit ifthere's no possible hits */

                if(rgn->base > end || rgn->size == 0)检查是否在当前的bank中且size是否为0

                       break;

 

                /* Checkif we are fully enclosed within an existing

                 * block

                 */

                if(rgn->base <= base && rend >= end)检查是否超过当前block范围

                       return 0;

 

                /* Checkif we overlap or are adjacent with the bottom

                 * of a block.

                 */

                if (base< rgn->base && end >= rgn->base) {

                        /*If we can't coalesce, create a new block */

                        if(!memblock_memory_can_coalesce(…这个函数一定返回1所以省去,下同

                       

                        /*We extend the bottom of the block down to our

                         *base

                        */

                       rgn->base = base;

                       rgn->size = rend - base;

 

                        /* Return if we have nothingelse to allocate

                         *(fully coalesced)

                        */

                        if(rend >= end)

                               return 0;

 

                        /*We continue processing from the end of the

                         *coalesced block.

                        */

                       base = rend;

                       size = end - base;

                        上面这一段就是把去掉低端重叠区,

                }

 

                /* Now check if we overlap or areadjacent with the

                 * top ofa block

                 */

                顶部的重叠区去处

                if (base<= rend && end >= rend) {

                        /*If we can't coalesce, create a new block */

                        if(!memblock_memory_can_coalesce(…

 

                        size += (base - rgn->base);

                       base = rgn->base;

                       memblock_remove_region(type, i--);

        for (i = r; i < type->cnt - 1; i++) {

               type->regions[i].base = type->regions[i + 1].base;

               type->regions[i].size = type->regions[i + 1].size;

        }

        type->cnt--;

        有重叠说明连续的,就把它合并到一起,

                }

        }

 

        /* If the array isempty, special case, replace the fake

         * filler regionand return

         */

        if ((type->cnt== 1) && (type->regions[0].size == 0)) {

我的平台现在调用会执行到这

               type->regions[0].base = base; 0x30000000

               type->regions[0].size = size; 0x4000000

                return 0;

        }

 

 new_block:新的block

        /* If we are outof space, we fail. It's too late to resize the array

         * but then thisshouldn't have happened in the first place.

    */

        if(WARN_ON(type->cnt >= type->max))超过最大就返回

                return -1;

 

        /* Couldn'tcoalesce the MEMBLOCK, so add it to the sorted table. */

        不能合并我们按顺序存到regions

        for (i =type->cnt - 1; i >= 0; i--) {

                if (base< type->regions[i].base) {

                       type->regions[i+1].base = type->regions[i].base;

                       type->regions[i+1].size = type->regions[i].size;

                } else {

                       type->regions[i+1].base = base;

                       type->regions[i+1].size = size;

                       slot = i + 1;

                       break;

                }

        }

        if (base <type->regions[0].base) {

               type->regions[0].base = base;

               type->regions[0].size = size;

                slot = 0;

        }

        type->cnt++;

 

        /* The array isfull ? Try to resize it. If that fails, we undo

         * our allocationand return an error

         */

        满了尝试重定义大小

        if (type->cnt== type->max && memblock_double_array(type)) {

               BUG_ON(slot < 0);

               memblock_remove_region(type, slot);

                return -1;

        }

 

        return 0;

}

 

看了源码其实就是把之前的bank信息存到memblock.memory.regions中。

 

        /* Register thekernel text, kernel data and initrd with memblock. */

Kernel XIP 原理如下,内核映像在Flash 设备上执行以后,只把映像中要读写的.data和.bss 拷贝到SDRAM 主存中,同时设置好系统的MMU,内核运行过程中,代码段.text 指向Flash 空间,.data 和.bss 指向SDRAM 主存空间。相对于全映射的执行方式,系统节省了解压缩和拷贝代码段的时间,节省了代码段占用的RAM 主存空间。

我的没有用这个东西。不过从下面你可以看到XIP没有吧text存入memblock

#ifdef CONFIG_XIP_KERNEL

       memblock_reserve(__pa(_sdata), _end - _sdata);

#else

        memblock_reserve(__pa(_stext),_end - _stext);

System.map

 

c00081e0 T _stext

c0318000 D _sdata

c0367db8 A _end

__pa(_stext) = 0x300081e0

 

#endif

long __init_memblock memblock_reserve(phys_addr_t base,phys_addr_t size)

{

        structmemblock_type *_rgn = &memblock.reserved;

 

        BUG_ON(0 == size);

 

        returnmemblock_add_region(_rgn, base, size);这个看上面

}

通过上面的我们可以算出,不过还是加个打印吧

       //wxl add

       printk(KERN_NOTICE "memory:\n");

       for (i = 0; i <memblock.memory.cnt; i++)

       {

           printk(KERN_NOTICE"regions[%d] base = %lx size = %lx\n", i, (unsignedlong)memblock.memory.regions[i].base, (unsigned long)memblock.memory.regions[i].size);

       }

 

       printk(KERN_NOTICE"reserved:\n");

       for (i = 0; i <memblock.reserved.cnt; i++)

       {

           printk(KERN_NOTICE"regions[%d] base = %lx size = %lx\n", i, (unsignedlong)memblock.reserved.regions[i].base, (unsigned long)memblock.reserved.regions[i].size);

       }

打印结果

memory:

regions[0] base = 30000000 size = 4000000

reserved:

regions[0] base = 300081e0 size = 35fbd8

用上面计算也是这个结果,到此reserved应该就是记录内核的大小,不过下面它还要做些事

 

下面和initrd的使用有关

为了能够使用RAM disk你的内核必须要支持RAMdisk,即:在编译内核时,要选中RAMdisk support这一选项,会在配置文件中定义CONFIG_BLK_DEV_RAM。
       为了让内核有能力在内核加载阶段就能装入RAMDISK,并运行其中的内容,要选中initial RAM disk(initrd) support 选项,会在配置文件中定义CONFIG_BLK_DEV_INITRD。

 

http://wenku.baidu.com/view/dc6dc785bceb19e8b8f6baba.html

此链接是一篇关于initramfsinitrd的文章,有兴趣看看

Initrd是一个临时文件系统。在某些没有存储设备的嵌入式系统中,initrd永久根文件系统

它就是个文件系统,不过很小

initrd 中包含了实现这个目标所需要的目录和可执行程序的最小集合,例如将内核模块加载到内核中所使用的 insmod 工具。

initrd 映像中包含了支持 Linux系统两阶段引导过程所需要的必要可执行程序和系统文件。

咱们看看它有什么

http://blog.163.com/dongfeng_114/blog/static/4664357420112452442211/

查看initrd的内容方法

我的是用cpio方法的,下面是我的pcinitrd的内容

[root@localhost tempfs]# ls

bin  dev  etc init  initrd.img  lib proc  sbin  sys sysroot

[root@localhost tempfs]# ls dev/

console  ptmx  ram1   tty   tty10  tty2 tty5  tty8   ttyS1 zero

mapper   ram   rtc    tty0  tty11  tty3 tty6  tty9   ttyS2

null     ram0  systty tty1  tty12  tty4 tty7  ttyS0  ttyS3

[root@localhost tempfs]# ls sbin/

dmraid  insmod  kpartx lvm  modprobe  nash

最后说一下uboot的bootargs 要设置initrd=addr,[Size]M

大家看看自己思考思考吧,我们看下面的内存处理

 

#ifdef CONFIG_BLK_DEV_INITRD

先说phys_initrd_size,它初始化定义是0

static int __init parse_tag_initrd(const struct tag *tag)

{

       printk(KERN_WARNING "ATAG_INITRD is deprecated; "

               "please update your bootloader.\n");

        phys_initrd_start= __virt_to_phys(tag->u.initrd.start);

        phys_initrd_size =tag->u.initrd.size;

        return 0;

}

 

__tagtable(ATAG_INITRD, parse_tag_initrd);

上面的东西看过我上一篇《linux内核启动1》应该不会陌生吧,就是把bootcmdlineroot=后面的赋值到phys_initrd_startphys_initrd_size

        if(phys_initrd_size &&

           !memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {

memblock_is_region_memory函数就是和memblock.memory比较看在不在此内存里面

                pr_err("INITRD:0x%08lx+0x%08lx is not a memory region - disabling initrd\n",

                      phys_initrd_start, phys_initrd_size);

如果你看到这个打印,就是initrd不在可用内核范围内

               phys_initrd_start = phys_initrd_size = 0;

        }

        if(phys_initrd_size &&

           memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {

memblock_is_region_reserved当然是和memblock.reserved比较

                pr_err("INITRD:0x%08lx+0x%08lx overlaps in-use memory region - disabling initrd\n",

                      phys_initrd_start, phys_initrd_size);

如果你看到这个打印,就是initrd和内核重叠了

               phys_initrd_start = phys_initrd_size = 0;

        }

上面的两个判断就是在可用内存内且不能和内核重叠

        if(phys_initrd_size) {

               memblock_reserve(phys_initrd_start, phys_initrd_size);上面已解释过

 

                /* Nowconvert initrd to virtual addresses */

               initrd_start = __phys_to_virt(phys_initrd_start);转换为虚拟地址

                initrd_end= initrd_start + phys_initrd_size;

        }

#endif

reserved又记录initrd信息

       arm_mm_memblock_reserve();

void __init arm_mm_memblock_reserve(void)

{

......

       memblock_reserve(__pa(swapper_pg_dir), SWAPPER_PG_DIR_SIZE);

……

存储页表空间

arm页表地址

c0004000 A swapper_pg_dir

arch/arm/kernel/head.S下

.equ    swapper_pg_dir,KERNEL_RAM_VADDR - PG_DIR_SIZE

#define PG_DIR_SIZE    0x4000

KERNEL_RAM_VADDR是0x30008000

       arm_dt_memblock_reserve();这个保存设备树的启动参数,不细看

 

        /* reserve anyplatform specific memblock areas */

        if(mdesc->reserve)对应平台自定义的block

               mdesc->reserve();

 

       memblock_analyze();更新memblockmemory_size的值

       memblock_dump_all();这个就是打印reservedmemory,和我中间加的打印东西差不多

}

<think>我们将使用v-viewer库在Vue3中实现一个功能丰富的图片查看器。v-viewer是一个基于viewer.js的Vue图片查看器组件,支持旋转、缩放、翻转、播放等操作。 步骤: 1. 安装v-viewerviewer.js 2. 在Vue3中全局注册v-viewer组件 3. 创建图片列表并使用v-viewer组件 4. 实现单击图片查看当前图片,并支持从第一张开始查看 代码实现:</think># Vue3中使用v-viewer实现高级图片预览功能 下面我将展示如何在Vue3中使用v-viewer库实现一个功能丰富的图片预览组件,支持单击预览、多图导航、缩放旋转等高级功能。 ## 解决方案思路 1. 安装并配置v-viewer库 2. 创建图片列表组件 3. 实现单图预览全图集预览功能 4. 添加自定义操作按钮(旋转、缩放、下载等) 5. 实现响应式设计加载状态处理 ## 完整代码实现 ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue3 + v-viewer 高级图片预览</title> <!-- 引入v-viewer样式 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/viewerjs@1.11.1/dist/viewer.min.css"> <script src="https://cdn.jsdelivr.net/npm/vue@3.2.36/dist/vue.global.js"></script> <script src="https://cdn.jsdelivr.net/npm/viewerjs@1.11.1/dist/viewer.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/v-viewer@1.6.4/dist/index.min.js"></script> <style> :root { --primary: #4361ee; --secondary: #3f37c9; --accent: #4895ef; --light: #f8f9fa; --dark: #212529; --success: #4cc9f0; --warning: #f72585; --gray: #6c757d; --border: #dee2e6; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: &#39;Segoe UI&#39;, Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); min-height: 100vh; padding: 20px; color: var(--dark); } .container { max-width: 1400px; margin: 40px auto; background: white; border-radius: 20px; box-shadow: 0 15px 50px rgba(0, 0, 0, 0.15); overflow: hidden; } header { background: linear-gradient(to right, var(--primary), var(--secondary)); color: white; padding: 30px 40px; text-align: center; } h1 { font-size: 2.8rem; margin-bottom: 15px; text-shadow: 0 2px 4px rgba(0,0,0,0.2); } .subtitle { font-size: 1.2rem; opacity: 0.9; max-width: 700px; margin: 0 auto; } .content { padding: 40px; } .features { display: flex; flex-wrap: wrap; justify-content: center; gap: 25px; margin: 40px 0; } .feature-card { width: 220px; padding: 25px; background: white; border-radius: 15px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08); text-align: center; transition: all 0.3s ease; border: 2px solid transparent; } .feature-card:hover { transform: translateY(-10px); box-shadow: 0 12px 20px rgba(67, 97, 238, 0.15); border-color: var(--accent); } .feature-icon { font-size: 48px; margin-bottom: 20px; color: var(--primary); } .feature-title { font-size: 1.3rem; margin-bottom: 10px; color: var(--dark); } .controls { display: flex; justify-content: center; gap: 20px; margin: 30px 0; flex-wrap: wrap; } .btn { padding: 14px 32px; font-size: 1.1rem; border-radius: 50px; border: none; cursor: pointer; transition: all 0.3s; font-weight: 600; display: inline-flex; align-items: center; gap: 10px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .btn-primary { background: linear-gradient(to right, var(--primary), var(--accent)); color: white; } .btn-primary:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(67, 97, 238, 0.3); } .btn-secondary { background: white; color: var(--primary); border: 2px solid var(--primary); } .btn-secondary:hover { background: var(--light); } .gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 25px; margin: 40px 0; } .gallery-item { border-radius: 15px; overflow: hidden; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); cursor: pointer; transition: all 0.3s ease; position: relative; height: 240px; } .gallery-item:hover { transform: translateY(-8px); box-shadow: 0 15px 30px rgba(67, 97, 238, 0.2); } .gallery-img { width: 100%; height: 100%; object-fit: cover; display: block; } .image-info { position: absolute; bottom: 0; left: 0; right: 0; background: rgba(0, 0, 0, 0.7); color: white; padding: 15px; transform: translateY(100%); transition: transform 0.3s ease; } .gallery-item:hover .image-info { transform: translateY(0); } .image-title { font-size: 1.2rem; margin-bottom: 5px; } .image-desc { font-size: 0.9rem; opacity: 0.8; } .status-bar { display: flex; justify-content: space-between; background: var(--light); padding: 15px 25px; border-radius: 12px; margin-top: 30px; font-size: 1.1rem; } footer { text-align: center; padding: 30px; background: var(--light); color: var(--gray); font-size: 1.1rem; } .custom-viewer { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 9999; background: rgba(0, 0, 0, 0.9); } .tech-stack { display: flex; justify-content: center; gap: 20px; margin: 30px 0; flex-wrap: wrap; } .tech-item { background: rgba(67, 97, 238, 0.1); color: var(--primary); padding: 8px 20px; border-radius: 30px; font-weight: 600; display: flex; align-items: center; gap: 8px; } @media (max-width: 768px) { .gallery { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); } h1 { font-size: 2.2rem; } } </style> </head> <body> <div id="app"> <div class="container"> <header> <h1>Vue3 + v-viewer 高级图片预览</h1> <p class="subtitle">实现单图预览、多图导航、缩放旋转等高级功能</p> <div class="tech-stack"> <div class="tech-item">Vue 3</div> <div class="tech-item">v-viewer</div> <div class="tech-item">Viewer.js</div> <div class="tech-item">CSS3</div> </div> </header> <div class="content"> <div class="features"> <div class="feature-card"> <div class="feature-icon">🔍</div> <h3 class="feature-title">缩放查看</h3> <p>支持鼠标滚轮缩放图片</p> </div> <div class="feature-card"> <div class="feature-icon">🔄</div> <h3 class="feature-title">旋转翻转</h3> <p>支持任意角度旋转水平翻转</p> </div> <div class="feature-card"> <div class="feature-icon">📷</div> <h3 class="feature-title">全屏模式</h3> <p>支持全屏查看图片细节</p> </div> <div class="feature-card"> <div class="feature-icon">⬇️</div> <h3 class="feature-title">下载图片</h3> <p>一键下载原始图片</p> </div> </div> <div class="controls"> <button class="btn btn-primary" @click="viewAll"> <span>查看全部图片</span> </button> <button class="btn btn-secondary" @click="shuffleImages"> <span>随机排序图片</span> </button> </div> <div class="gallery"> <div v-for="(image, index) in images" :key="image.id" class="gallery-item" @click="viewImage(index)" > <img :src="image.thumbnail" :alt="image.title" class="gallery-img"> <div class="image-info"> <div class="image-title">{{ image.title }}</div> <div class="image-desc">{{ image.desc }}</div> </div> </div> </div> <div class="status-bar"> <div>图片数量: {{ images.length }} 张</div> <div>当前预览模式: {{ previewMode }}</div> <div>最后操作: {{ lastAction || &#39;无&#39; }}</div> </div> </div> <footer> Vue3 + v-viewer 图片预览组件 | 支持高级图片查看功能 </footer> </div> <!-- v-viewer 组件 --> <viewer :images="previewList" :options="viewerOptions" @inited="viewerInited" ref="viewerRef"> <template #default="scope"> <div v-for="(src, index) in scope.images" :key="index" style="display: none;"> <img :src="src" :data-source="src"> </div> </template> </viewer> </div> <script> const { createApp, ref, reactive, computed, onMounted } = Vue; const app = createApp({ setup() { // 图片数据 const images = ref([ { id: 1, thumbnail: &#39;https://images.unsplash.com/photo-1682687220067-dced9a881b56?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY5MTY0NjQ1MQ&ixlib=rb-4.0.3&q=80&w=400&#39;, full: &#39;https://images.unsplash.com/photo-1682687220067-dced9a881b56?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY5MTY0NjQ1MQ&ixlib=rb-4.0.3&q=80&w=1200&#39;, title: &#39;山间日出&#39;, desc: &#39;清晨的第一缕阳光穿过山谷&#39; }, { id: 2, thumbnail: &#39;https://images.unsplash.com/photo-1682687220208-22d7a2543e88?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY5MTY0NjQ1MQ&ixlib=rb-4.0.3&q=80&w=400&#39;, full: &#39;https://images.unsplash.com/photo-1682687220208-22d7a2543e88?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY5MTY0NjQ1MQ&ixlib=rb-4.0.3&q=80&w=1200&#39;, title: &#39;沙漠风光&#39;, desc: &#39;无垠沙丘与蓝天相接&#39; }, { id: 3, thumbnail: &#39;https://images.unsplash.com/photo-1682687220923-471809f7e9c0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY5MTY0NjQ1MQ&ixlib=rb-4.0.3&q=80&w=400&#39;, full: &#39;https://images.unsplash.com/photo-1682687220923-471809f7e9c0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY5MTY0NjQ1MQ&ixlib=rb-4.0.3&q=80&w=1200&#39;, title: &#39;海滩日落&#39;, desc: &#39;夕阳下的金色海岸线&#39; }, { id: 4, thumbnail: &#39;https://images.unsplash.com/photo-1682687220509-61b8a906ca19?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY5MTY0NjQ1MQ&ixlib=rb-4.0.3&q=80&w=400&#39;, full: &#39;https://images.unsplash.com/photo-1682687220509-61b8a906ca19?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY5MTY0NjQ1MQ&ixlib=rb-4.0.3&q=80&w=1200&#39;, title: &#39;森林小径&#39;, desc: &#39;绿意盎然的林间小路&#39; }, { id: 5, thumbnail: &#39;https://images.unsplash.com/photo-1682687220509-61b8a906ca19?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY5MTY0NjQ1MQ&ixlib=rb-4.0.3&q=80&w=400&#39;, full: &#39;https://images.unsplash.com/photo-1682687220509-61b8a906ca19?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY5MTY0NjQ1MQ&ixlib=rb-4.0.3&q=80&w=1200&#39;, title: &#39;雪山湖泊&#39;, desc: &#39;雪山倒映在清澈湖水中&#39; }, { id: 6, thumbnail: &#39;https://images.unsplash.com/photo-1682687220509-61b8a906ca19?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY5MTY0NjQ1MQ&ixlib=rb-4.0.3&q=80&w=400&#39;, full: &#39;https://images.unsplash.com/photo-1682687220509-61b8a906ca19?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY5MTY0NjQ1MQ&ixlib=rb-4.0.3&q=80&w=1200&#39;, title: &#39;城市夜景&#39;, desc: &#39;灯火辉煌的都市夜晚&#39; } ]); // 预览状态 const previewMode = ref(null); const lastAction = ref(null); const viewerRef = ref(null); // 计算预览列表 const previewList = computed(() => { return images.value.map(img => img.full); }); // Viewer.js 配置选项 const viewerOptions = reactive({ inline: false, button: true, navbar: true, title: true, toolbar: { zoomIn: 1, zoomOut: 1, oneToOne: 1, reset: 1, prev: 1, play: { show: 1, size: &#39;large&#39;, }, next: 1, rotateLeft: 1, rotateRight: 1, flipHorizontal: 1, flipVertical: 1, }, viewed(ev) { lastAction.value = `查看图片: ${images.value[ev.detail.index].title}`; }, rotate(ev) { lastAction.value = `旋转图片: ${ev.detail.degree}°`; }, flip(ev) { lastAction.value = `翻转图片: ${ev.detail.direction === &#39;horizontal&#39; ? &#39;水平&#39; : &#39;垂直&#39;}`; } }); // 查看单张图片 const viewImage = (index) => { previewMode.value = &#39;单图预览&#39;; if (viewerRef.value && viewerRef.value.$viewer) { viewerRef.value.$viewer.view(index); } }; // 查看所有图片 const viewAll = () => { previewMode.value = &#39;全图集预览&#39;; if (viewerRef.value && viewerRef.value.$viewer) { viewerRef.value.$viewer.show(); } }; // 随机排序图片 const shuffleImages = () => { images.value = [...images.value].sort(() => Math.random() - 0.5); lastAction.value = &#39;随机排序图片&#39;; }; // Viewer初始化回调 const viewerInited = (viewer) => { console.log(&#39;v-viewer 已初始化&#39;); // 添加自定义按钮 viewer.toolbar.appendChild(createDownloadButton(viewer)); }; // 创建下载按钮 const createDownloadButton = (viewer) => { const button = document.createElement(&#39;button&#39;); button.className = &#39;viewer-download&#39;; button.innerHTML = &#39;⬇️&#39;; button.title = &#39;下载图片&#39;; button.style.marginLeft = &#39;10px&#39;; button.style.fontSize = &#39;24px&#39;; button.style.background = &#39;none&#39;; button.style.border = &#39;none&#39;; button.style.cursor = &#39;pointer&#39;; button.style.color = &#39;#fff&#39;; button.addEventListener(&#39;click&#39;, () => { const image = viewer.image; if (image) { const link = document.createElement(&#39;a&#39;); link.href = image.src; link.download = `image-${new Date().getTime()}.jpg`; link.click(); lastAction.value = &#39;下载图片&#39;; } }); return button; }; onMounted(() => { console.log(&#39;图片预览组件已加载&#39;); }); return { images, previewMode, lastAction, previewList, viewerOptions, viewerRef, viewImage, viewAll, shuffleImages, viewerInited }; } }); // 使用v-viewer插件 app.use(VViewer); app.mount(&#39;#app&#39;); </script> </body> </html> ``` ## 代码解释 ### 1. 安装配置v-viewer 首先需要安装v-viewer库: ```bash npm install v-viewer viewerjs ``` 在Vue3中全局注册: ```javascript import { createApp } from &#39;vue&#39; import App from &#39;./App.vue&#39; import VViewer from &#39;v-viewer&#39; import &#39;viewerjs/dist/viewer.css&#39; const app = createApp(App) app.use(VViewer) app.mount(&#39;#app&#39;) ``` ### 2. 使用v-viewer组件 ```html <viewer :images="previewList" :options="viewerOptions" @inited="viewerInited" ref="viewerRef" > <template #default="scope"> <div v-for="(src, index) in scope.images" :key="index" style="display: none;"> <img :src="src" :data-source="src"> </div> </template> </viewer> ``` - `:images`:绑定要预览的图片列表 - `:options`:配置Viewer.js的选项 - `@inited`:Viewer初始化完成的回调 - `ref`:获取Viewer实例引用 ### 3. Viewer.js配置选项 ```javascript const viewerOptions = reactive({ inline: false, button: true, navbar: true, title: true, toolbar: { zoomIn: 1, zoomOut: 1, oneToOne: 1, reset: 1, prev: 1, play: { show: 1, size: &#39;large&#39; }, next: 1, rotateLeft: 1, rotateRight: 1, flipHorizontal: 1, flipVertical: 1, }, viewed(ev) { // 图片查看事件 }, rotate(ev) { // 图片旋转事件 }, flip(ev) { // 图片翻转事件 } }); ``` ### 4. 图片预览功能实现 **单图预览**: ```javascript const viewImage = (index) => { previewMode.value = &#39;单图预览&#39;; if (viewerRef.value && viewerRef.value.$viewer) { viewerRef.value.$viewer.view(index); } }; ``` **全图集预览**: ```javascript const viewAll = () => { previewMode.value = &#39;全图集预览&#39;; if (viewerRef.value && viewerRef.value.$viewer) { viewerRef.value.$viewer.show(); } }; ``` ### 5. 自定义下载按钮 ```javascript const createDownloadButton = (viewer) => { const button = document.createElement(&#39;button&#39;); // ...按钮样式设置 button.addEventListener(&#39;click&#39;, () => { const image = viewer.image; if (image) { const link = document.createElement(&#39;a&#39;); link.href = image.src; link.download = `image-${new Date().getTime()}.jpg`; link.click(); } }); return button; }; // 在初始化回调中添加按钮 const viewerInited = (viewer) => { viewer.toolbar.appendChild(createDownloadButton(viewer)); }; ``` ## 功能特点 1. **多种预览模式**: - 单图预览:单击图片查看当前图片 - 全图集预览:从第一张开始顺序查看所有图片 2. **丰富的图片操作**: - 缩放(放大、缩小、1:1比例) - 旋转(左右旋转90度) - 翻转(水平垂直) - 幻灯片播放 - 图片下载 3. **响应式设计**: - 适应不同屏幕尺寸 - 移动设备友好 4. **用户体验优化**: - 图片悬停效果 - 操作记录显示 - 平滑的动画过渡 ## 技术优势 1. **v-viewer特性**: - 基于强大的Viewer.js库 - 支持所有Viewer.js的功能配置 - 无缝集成到Vue3应用中 2. **Vue3优势**: - 使用Composition API组织代码 - 响应式数据管理 - 组件化开发
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值