├── .babelrc ├── .gitignore ├── .npmignore ├── README.md ├── build ├── components │ └── photoGallery │ │ ├── canvas.d.ts │ │ ├── components │ │ ├── Canvas.d.ts │ │ ├── Footer.d.ts │ │ ├── Image.d.ts │ │ ├── Sidebar.d.ts │ │ └── index.d.ts │ │ └── index.d.ts ├── index.d.ts └── utils │ ├── img.d.ts │ └── utils.d.ts ├── config ├── demo.js ├── webpack.base.js ├── webpack.dev.config.js └── webpack.prod.config.js ├── example ├── image │ └── img-gallery.gif └── src │ ├── app.js │ ├── index.html │ ├── index.less │ └── index.less.d.ts ├── lib ├── 58640177ac8479591e07836acf38b280.ttf ├── 5e7cb895895e7d9a1f42692c25f0f89e.svg ├── 6ca395623c66716ea37499830cca838e.eot ├── d3af303528816fafe00926a29dba3a50.woff ├── index.js └── main.min.css ├── package-lock.json ├── package.json ├── src ├── components │ └── photoGallery │ │ ├── canvas.ts │ │ ├── components │ │ ├── Canvas.tsx │ │ ├── Footer.tsx │ │ ├── Image.tsx │ │ ├── Sidebar.tsx │ │ ├── canvas.less │ │ ├── canvas.less.d.ts │ │ ├── footer.less │ │ ├── footer.less.d.ts │ │ ├── image.less │ │ ├── image.less.d.ts │ │ ├── index.ts │ │ ├── sidebar.less │ │ └── sidebar.less.d.ts │ │ ├── fonts │ │ ├── iconfont.eot │ │ ├── iconfont.less │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ │ ├── index.less │ │ ├── index.less.d.ts │ │ └── index.tsx ├── images │ ├── images.d.ts │ └── loading.gif ├── index.html ├── index.less ├── index.tsx ├── types │ └── global │ │ └── window.d.ts └── utils │ ├── img.ts │ └── utils.ts ├── tsconfig.json └── tslint.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrell0904/darrell-photo-gallery/f008fb722b9414966ac9c9c357f34e9561145291/.npmignore -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # darrell-photo-gallery 2 | 3 | > 因为最近工作需要预览制作一个图片预览的插件,在查看了很多产品(掘金、知乎、简书、石墨等)的图片预览之后,还是觉得石墨的图片预览符合我们的产品需求。 4 | 5 | 6 | 7 | ## 介绍 8 | 9 | 这是一个基于 **React Hoos** 开发的 **仿石墨的图片** 放大预览插件。 10 | 11 | 在社区找了很久之后,没有找到相关的轮子,于是自己撸了一个。 12 | 13 | 插件的功能是当你的设备支持 `canvas` 的时候,就使用 `canvas` 进行绘图,当不支持 `canvas` 的时候就直接显示图片。 14 | 15 | 16 | 17 | ## 支持功能 18 | 19 | - [x] 放大图片 20 | - [x] 缩小图片 21 | - [x] 原尺寸大小显示 22 | - [x] 适应屏幕 23 | - [ ] 下载图片 24 | 25 | 26 | 27 | ## 预览 28 | 29 | ![](./example/image/img-gallery.gif) 30 | 31 | 32 | 33 | ## 安装 34 | 35 | ```javascript 36 | // 安装 37 | npm install darrell-photo-gallery -D 38 | 39 | // 在项目中使用 40 | 41 | import PhotoGallery from 'darrell-photo-gallery'; 42 | import 'darrell-photo-gallery/lib/main.min.css'; 43 | ``` 44 | 45 | 46 | 47 | ## 使用 48 | 49 | ```javascript 50 | { 56 | setVisible(false); 57 | } 58 | } 59 | /> 60 | ``` 61 | 62 | 63 | 64 | 目前支持四个参数: 65 | 66 | * `visible`:`boolean`,预览插件的显示隐藏 67 | 68 | * `imgData`:`object`,具体属性如下 69 | 70 | ```js 71 | interface Img { 72 | url: string; 73 | size?: string; 74 | width?: string; 75 | height?: string; 76 | [propName: string]: any; 77 | } 78 | ``` 79 | 80 | * `currentImg`:`number`,当前图片在图片数组中是第几张 81 | 82 | * `hideModal`:`hideModal?(): void;`,是一个函数,关闭图片预览弹窗 83 | 84 | 85 | 86 |   87 | 88 | ## 原理介绍 89 | 90 | 原理介绍大家可以移步笔者的掘金文章:[使用 React Hooks 实现仿石墨的图片预览插件(巨详细)](https://juejin.im/post/5e9bf299f265da47ee3f6c31) 91 | 92 | -------------------------------------------------------------------------------- /build/components/photoGallery/canvas.d.ts: -------------------------------------------------------------------------------- 1 | interface CanvasOptions { 2 | imgUrl: string; 3 | winWidth: number; 4 | winHeight: number; 5 | canUseCanvas: boolean; 6 | loadingComplete?(instance: any): void; 7 | } 8 | declare class ImgToCanvas { 9 | private options; 10 | private el; 11 | private cacheCanvas; 12 | private canUseCanvas; 13 | private context; 14 | private image; 15 | private imgUrl; 16 | private imgTop; 17 | private imgLeft; 18 | private LongImgTop; 19 | private LongImgLeft; 20 | private sidebarWidth; 21 | private footerHeight; 22 | private cImgWidth; 23 | private cImgHeight; 24 | private winWidth; 25 | private winHeight; 26 | private cWidth; 27 | private cHeight; 28 | private curPos; 29 | private curScaleIndex; 30 | fixScreenSize: number; 31 | private EachSizeWidthArray; 32 | private isDoCallback; 33 | constructor(selector: any, options: CanvasOptions); 34 | /** 35 | * 图片初始化 36 | */ 37 | private imageInit; 38 | /** 39 | * 设置图片的样式 40 | */ 41 | setImgStyle(type?: string): void; 42 | private canvasInit; 43 | private drawImg; 44 | /** 45 | * 设置当前 EachSizeWidthArray 的索引,用于 放大缩小 46 | */ 47 | private setCurScaleIndex; 48 | /** 49 | * 修改当前 图片大小数组中的 索引 50 | * @param curSizeIndex : 51 | */ 52 | changeCurSizeIndex(curSizeIndex: number): void; 53 | /** 54 | * 清除画布 55 | * @param ctx 56 | */ 57 | private clearLastCanvas; 58 | /** 59 | * 滚轮事件 60 | * @param e wheel 的事件参数 61 | */ 62 | WheelUpdate(e: any): void; 63 | /** 64 | * 鼠标拖动的事件 65 | * @param moveFlag : 是否能移动的标志位 66 | * @param e 67 | */ 68 | MoveCanvas(moveFlag: boolean, e: any): void; 69 | /** 70 | * 加载图片 71 | * @param callback :图片加载完成后要做的事情 72 | */ 73 | private loadimg; 74 | /** 75 | * 计算图片放大、缩小各尺寸的大小数组, 76 | */ 77 | private setEachSizeArr; 78 | } 79 | export default ImgToCanvas; 80 | -------------------------------------------------------------------------------- /build/components/photoGallery/components/Canvas.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | interface winSize { 3 | width: number; 4 | height: number; 5 | } 6 | interface Props { 7 | WinSize: winSize; 8 | canUseCanvas: boolean; 9 | curSize: number; 10 | imgUrl: string; 11 | imgLoading: boolean; 12 | setCurSize(cueSize: number): void; 13 | setImgLoading(bool: boolean): void; 14 | setFixScreenSize(fixScreenSize: number): void; 15 | WheelUpdate(e: any, instance: any): void; 16 | MouseDown(e: any, instance: any): void; 17 | MouseMove(e: any, instance: any): void; 18 | MouseUp(e: any): void; 19 | Click(): void; 20 | hideModal?(): void; 21 | } 22 | declare const Canvas: (props: Props) => JSX.Element; 23 | export default Canvas; 24 | -------------------------------------------------------------------------------- /build/components/photoGallery/components/Footer.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | interface Props { 3 | curSize: number; 4 | fixScreenSize: number; 5 | imgsLens: number; 6 | currentImgIndex: number; 7 | setCurSize(cueSize: number): void; 8 | } 9 | declare const Footer: (props: Props) => JSX.Element; 10 | export default Footer; 11 | -------------------------------------------------------------------------------- /build/components/photoGallery/components/Image.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | interface winSize { 3 | width: number; 4 | height: number; 5 | } 6 | interface Props { 7 | WinSize: winSize; 8 | imgUrl: string; 9 | canUseCanvas: boolean; 10 | WheelUpdate(e: any, instance: any): void; 11 | MouseDown(e: any, instance: any): void; 12 | MouseMove(e: any, instance: any): void; 13 | MouseUp(e: any): void; 14 | Click(): void; 15 | } 16 | declare const Image: (props: Props) => JSX.Element; 17 | export default Image; 18 | -------------------------------------------------------------------------------- /build/components/photoGallery/components/Sidebar.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | interface recordImg { 3 | url: string; 4 | size?: string; 5 | width?: string; 6 | height?: string; 7 | is_onlocal?: string; 8 | } 9 | interface Props { 10 | imgData: recordImg[]; 11 | curImgUrl?: string; 12 | setCurrentImgIndex(index: number): void; 13 | setImgLoading(bool: boolean): void; 14 | setImgUrl(imgUrl: string): void; 15 | } 16 | declare const Sidebar: (props: Props) => JSX.Element; 17 | export default Sidebar; 18 | -------------------------------------------------------------------------------- /build/components/photoGallery/components/index.d.ts: -------------------------------------------------------------------------------- 1 | import Footer from './Footer'; 2 | import Sidebar from './Sidebar'; 3 | import Canvas from './Canvas'; 4 | import Image from './Image'; 5 | export { Footer, Sidebar, Canvas, Image, }; 6 | -------------------------------------------------------------------------------- /build/components/photoGallery/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import "@components/photoGallery/fonts/iconfont.less"; 3 | interface recordImg { 4 | url: string; 5 | size?: string; 6 | width?: string; 7 | height?: string; 8 | is_onlocal?: string; 9 | [propName: string]: any; 10 | } 11 | interface Props { 12 | visible: boolean; 13 | currentImg: number; 14 | imgData: recordImg[]; 15 | hideModal?(): void; 16 | } 17 | declare const photoGallery: (props: Props) => JSX.Element; 18 | export default photoGallery; 19 | -------------------------------------------------------------------------------- /build/index.d.ts: -------------------------------------------------------------------------------- 1 | import PhotoGallery from './components/photoGallery/index'; 2 | export default PhotoGallery; 3 | -------------------------------------------------------------------------------- /build/utils/img.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取图片在 显示区域中 的 宽高,top,left 3 | */ 4 | interface RectWidth { 5 | naturalWidth: number; 6 | naturalHeight: number; 7 | wrapperWidth: number; 8 | wrapperHeight: number; 9 | curImgWidth?: number; 10 | curImgHeight?: number; 11 | curImgTop?: number; 12 | curImgLeft?: number; 13 | winWidth?: number; 14 | } 15 | interface BoundingClientRect { 16 | imgTop: number; 17 | imgLeft: number; 18 | ImgHeight: number; 19 | ImgWidth: number; 20 | } 21 | interface NaturalImg { 22 | naturalWidth: number; 23 | naturalHeight: number; 24 | } 25 | export declare const getBoundingClientRect: (options: RectWidth) => BoundingClientRect; 26 | /** 27 | * 获取各尺寸的图片大小,默认最大是原图的 4 倍,最小是原图的 1/10;总共能放大缩小六次 28 | */ 29 | export declare const getEachSizeWidthArray: (options: NaturalImg) => number[]; 30 | /** 31 | * 获取适应屏幕 的 sizeIndex 32 | * @params EachSizeWidthArray:各尺寸的图片宽度 33 | */ 34 | export declare const getFixScreenIndex: (options: RectWidth, EachSizeWidthArray: number[]) => number; 35 | /** 36 | * 获取当前的 curIndex 37 | * @params EachSizeWidthArray:各尺寸的图片宽度 38 | * @params CurImgWidth:当前页面的大小 39 | */ 40 | export declare const getCurImgIndex: (EachSizeWidthArray: number[], CurImgWidth: number) => number; 41 | export {}; 42 | -------------------------------------------------------------------------------- /build/utils/utils.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断能否使用 canvas 3 | */ 4 | export declare const canUseCanvas = true; 5 | /** 6 | * 二分查找 离数组最近的一个元素 7 | */ 8 | export declare const findCloseSet: (arr: number[], num: number) => number; 9 | /** 10 | * requestAnimationFrame 11 | */ 12 | export declare const requestAnimationFrame: ((callback: FrameRequestCallback) => number) & ((callback: FrameRequestCallback) => number); 13 | /** 14 | * cancelAnimationFrame 15 | */ 16 | export declare const cancelAnimationFrame: ((handle: number) => void) & ((handle: number) => void); 17 | -------------------------------------------------------------------------------- /config/demo.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const merge = require('webpack-merge'); 4 | const baseConfig = require('./webpack.base.js'); // 引用公共配置 5 | const HtmlWebpackPlugin = require('html-webpack-plugin') 6 | 7 | const devConfig = { 8 | mode: 'development', // 开发模式 9 | devtool: 'cheap-module-eval-source-map', 10 | entry: path.join(__dirname, '../src/index'), // 项目入口,处理资源文件的依赖关系 11 | output: { 12 | path: path.join(__dirname, "../example/src/"), 13 | filename: "bundle.js", // 使用webpack-dev-sevrer启动开发服务时,并不会实际在`src`目录下生成bundle.js,打包好的文件是在内存中的,但并不影响我们使用。 14 | }, 15 | resolve: { 16 | // 一定不要忘记配置ts tsx后缀 17 | extensions: ['.tsx', '.ts', '.js'], 18 | alias: { 19 | '@utils': path.resolve('./src/utils'), // 这样配置后 utils 可以指向 src/utils 目录 20 | '@images': path.resolve('./src/images'), // 这样配置后 utils 可以指向 src/images 目录 21 | } 22 | }, 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.css$/, 27 | exclude: /\.min\.css$/, 28 | loader: [ 29 | 'style-loader', 30 | 'css-loader', 31 | 'typed-css-modules-loader', 32 | ], 33 | }, { 34 | test: /\.min\.css$/, 35 | loader: [ 36 | 'style-loader', 37 | 'css-loader' 38 | ], 39 | }, { 40 | test: /\.less$/, 41 | exclude: [ 42 | /node_modules/, 43 | path.resolve(__dirname, "../src/components/photoGallery/fonts"), 44 | ], 45 | use: [ 46 | 'style-loader', 47 | { 48 | loader: '@teamsupercell/typings-for-css-modules-loader', 49 | options: { 50 | formatter: "prettier", 51 | } 52 | }, 53 | { 54 | loader: 'css-loader', 55 | options: { 56 | modules: { 57 | localIdentName: '[local]_[hash:base64:5]', 58 | }, 59 | sourceMap: true, 60 | importLoaders: 2, 61 | localsConvention: 'camelCase' 62 | } 63 | }, 64 | // 'typed-css-modules-loader', 65 | 'less-loader', 66 | ] 67 | }, { 68 | test: /\.less$/, 69 | include: path.resolve(__dirname, "../src/components/photoGallery/fonts"), 70 | use: [ 71 | 'style-loader', 72 | 'css-loader', 73 | 'less-loader', 74 | ] 75 | }, { 76 | test: /\.(eot|woff2?|woff|ttf|svg|otf)$/, 77 | use: ['file-loader'], 78 | }, { 79 | test: /\.(png|jpg|gif)$/, 80 | use: { 81 | loader: 'url-loader', 82 | options: { 83 | limit: 8192, 84 | name: '[name]_[hash:7].[ext]', 85 | outputPath: 'images/', 86 | esModule: false, // 出现图片 src 是 module 对象 87 | } 88 | } 89 | }, 90 | ] 91 | }, 92 | plugins: [ 93 | new HtmlWebpackPlugin({ 94 | template: path.join(__dirname, '../src/index.html'), 95 | filename: 'index.html', 96 | }), 97 | new webpack.WatchIgnorePlugin([ 98 | /css\.d\.ts$/ 99 | ]), 100 | ], 101 | devServer: { 102 | // contentBase: path.join(__dirname, './src/App.tsx'), 103 | // compress: true, 104 | port: 3001, // 启动端口为 3001 的服务 105 | open: true // 自动打开浏览器 106 | }, 107 | }; 108 | module.exports = merge(devConfig, baseConfig); // 将baseConfig和devConfig合并为一个配置 109 | -------------------------------------------------------------------------------- /config/webpack.base.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | module: { 5 | rules: [ 6 | { 7 | // 使用 babel-loader 来编译处理 js 和 jsx 文件 8 | test: /\.(js|jsx)$/, 9 | use: "babel-loader", 10 | exclude: /node_modules/ 11 | }, { 12 | test: /\.(ts|tsx)?$/, 13 | use: ['ts-loader'], 14 | exclude: /node_modules/, 15 | }, { 16 | test: /\.(ts|tsx)?$/, 17 | include: path.join(__dirname , 'src'), 18 | exclude: /node_modules/, 19 | use: [{ 20 | loader: 'tslint-loader', 21 | options: {}, 22 | }], 23 | } 24 | ] 25 | }, 26 | }; -------------------------------------------------------------------------------- /config/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const merge = require('webpack-merge'); 4 | const baseConfig = require('./webpack.base.js'); // 引用公共配置 5 | const HtmlWebpackPlugin = require('html-webpack-plugin') 6 | 7 | const devConfig = { 8 | mode: 'development', // 开发模式 9 | devtool: 'cheap-module-eval-source-map', 10 | entry: path.join(__dirname, '../example/src/app.js'), // 项目入口,处理资源文件的依赖关系 11 | output: { 12 | path: path.join(__dirname, "../example/src/"), 13 | filename: "bundle.js", // 使用 webpack-dev-sevrer 启动开发服务时,并不会实际在`src`目录下生成bundle.js,打包好的文件是在内存中的,但并不影响我们使用。 14 | }, 15 | resolve: { 16 | // 一定不要忘记配置ts tsx后缀 17 | extensions: ['.tsx', '.ts', '.js'], 18 | alias: { 19 | '@utils': path.resolve('./src/utils'), // 这样配置后 utils 可以指向 src/utils 目录 20 | '@images': path.resolve('./src/images'), // 这样配置后 utils 可以指向 src/images 目录 21 | '@components': path.resolve('./src/components'), // 这样配置后 utils 可以指向 src/images 目录 22 | } 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.css$/, 28 | exclude: /\.min\.css$/, 29 | loader: [ 30 | 'style-loader', 31 | 'css-loader', 32 | 'typed-css-modules-loader', 33 | ], 34 | }, { 35 | test: /\.min\.css$/, 36 | loader: [ 37 | 'style-loader', 38 | 'css-loader', 39 | ], 40 | }, { 41 | test: /\.less$/, 42 | exclude: [ 43 | /node_modules/, 44 | path.resolve(__dirname, "../src/components/photoGallery/fonts"), 45 | ], 46 | use: [ 47 | 'style-loader', 48 | { 49 | loader: '@teamsupercell/typings-for-css-modules-loader', 50 | options: { 51 | formatter: "prettier", 52 | } 53 | }, 54 | { 55 | loader: 'css-loader', 56 | options: { 57 | modules: { 58 | localIdentName: '[local]_[hash:base64:5]', 59 | }, 60 | sourceMap: true, 61 | importLoaders: 2, 62 | localsConvention: 'camelCase' 63 | } 64 | }, 65 | // 'typed-css-modules-loader', 66 | 'less-loader', 67 | ] 68 | }, { 69 | test: /\.less$/, 70 | include: path.resolve(__dirname, "../src/components/photoGallery/fonts"), 71 | use: [ 72 | 'style-loader', 73 | 'css-loader', 74 | 'less-loader', 75 | ] 76 | }, { 77 | test: /\.(eot|woff2?|woff|ttf|svg|otf)$/, 78 | use: ['file-loader'], 79 | }, { 80 | test: /\.(png|jpg|gif)$/, 81 | use: { 82 | loader: 'url-loader', 83 | options: { 84 | limit: 8192, 85 | name: '[name]_[hash:7].[ext]', 86 | outputPath: 'images/', 87 | esModule: false, // 出现图片 src 是 module 对象 88 | } 89 | } 90 | }, 91 | ] 92 | }, 93 | plugins: [ 94 | new HtmlWebpackPlugin({ 95 | template: path.join(__dirname, '../src/index.html'), 96 | filename: 'index.html', 97 | }), 98 | new webpack.WatchIgnorePlugin([ 99 | /css\.d\.ts$/ 100 | ]), 101 | ], 102 | devServer: { 103 | contentBase: path.join(__dirname, '../example/src/'), 104 | compress: true, 105 | port: 3002, // 启动端口为 3001 的服务 106 | open: true // 自动打开浏览器 107 | }, 108 | }; 109 | module.exports = merge(devConfig, baseConfig); // 将baseConfig和devConfig合并为一个配置 110 | -------------------------------------------------------------------------------- /config/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const merge = require('webpack-merge'); 3 | const baseConfig = require('./webpack.base.js'); // 引用公共的配置 4 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 用于将组件的css打包成单独的文件输出到`lib`目录中 5 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 6 | 7 | const prodConfig = { 8 | mode: 'production', // 开发模式 9 | entry: path.join(__dirname, "../src/components/photoGallery/index"), 10 | output: { 11 | path: path.join(__dirname, "../lib/"), 12 | filename: "index.js", 13 | libraryTarget: 'umd', // 采用通用模块定义 14 | libraryExport: 'default', // 兼容 ES6 的模块系统、CommonJS 和 AMD 模块规范 15 | }, 16 | resolve: { 17 | // 一定不要忘记配置ts tsx后缀 18 | extensions: ['.tsx', '.ts', '.js'], 19 | alias: { 20 | '@utils': path.resolve('./src/utils'), // 这样配置后 utils 可以指向 src/utils 目录 21 | '@images': path.resolve('./src/images'), // 这样配置后 utils 可以指向 src/images 目录 22 | '@components': path.resolve('./src/components'), // 这样配置后 utils 可以指向 src/images 目录 23 | } 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.css$/, 29 | loader: [ 30 | MiniCssExtractPlugin.loader, 31 | 'css-loader?modules' 32 | ], 33 | }, 34 | { 35 | test: /\.less$/, 36 | exclude: [ 37 | /node_modules/, 38 | path.resolve(__dirname, "../src/components/photoGallery/fonts"), 39 | ], 40 | loader: [ 41 | MiniCssExtractPlugin.loader, 42 | { 43 | loader: 'css-loader', 44 | options: { 45 | modules: { 46 | localIdentName: '[local]_[hash:base64:5]', 47 | }, 48 | sourceMap: true, 49 | importLoaders: 2, 50 | localsConvention: 'camelCase' 51 | } 52 | }, 53 | 'less-loader', 54 | ], 55 | }, { 56 | test: /\.less$/, 57 | include: path.resolve(__dirname, "../src/components/photoGallery/fonts"), 58 | use: [ 59 | MiniCssExtractPlugin.loader, 60 | 'css-loader', 61 | 'less-loader', 62 | ] 63 | }, { 64 | test: /\.(eot|woff2?|woff|ttf|svg|otf)$/, 65 | use: ['file-loader'], 66 | }, { 67 | test: /\.(png|jpg|gif)$/, 68 | use: { 69 | loader: 'url-loader', 70 | options: { 71 | limit: 8192, 72 | name: '[name]_[hash:7].[ext]', 73 | outputPath: 'images/', 74 | esModule: false, // 出现图片 src 是 module 对象 75 | } 76 | } 77 | }, 78 | ] 79 | }, 80 | plugins: [ 81 | new MiniCssExtractPlugin({ 82 | filename: "main.min.css" // 提取后的css的文件名 83 | }), 84 | new CleanWebpackPlugin(), 85 | ], 86 | externals: { // 定义外部依赖,避免把react和react-dom打包进去 87 | react: { 88 | root: "React", 89 | commonjs2: "react", 90 | commonjs: "react", 91 | amd: "react" 92 | }, 93 | "react-dom": { 94 | root: "ReactDOM", 95 | commonjs2: "react-dom", 96 | commonjs: "react-dom", 97 | amd: "react-dom" 98 | } 99 | }, 100 | }; 101 | 102 | module.exports = merge(prodConfig, baseConfig); // 将baseConfig和prodConfig合并为一个配置 103 | -------------------------------------------------------------------------------- /example/image/img-gallery.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrell0904/darrell-photo-gallery/f008fb722b9414966ac9c9c357f34e9561145291/example/image/img-gallery.gif -------------------------------------------------------------------------------- /example/src/app.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { render } from 'react-dom' 3 | import styles from "./index.less"; 4 | import PhotoGallery from '../../src/index'; // 引入组件 5 | // import PhotoGallery from 'darrell-photo-gallery'; 6 | // import 'darrell-photo-gallery/lib/main.min.css'; 7 | 8 | const ImgData = [ 9 | { 10 | "url": "https://images-cdn.shimo.im/ZrtwqVbbrlU8VC4Y/50_.png__original", 11 | "size": "278452", 12 | "width": "1410", 13 | "height": "1410", 14 | "is_onlocal": "0" 15 | }, 16 | { 17 | "url": "https://uploader.shimo.im/f/fxr0tojKkcIHxEFX.png!original", 18 | "size": "290957", 19 | "width": "1410", 20 | "height": "1410", 21 | "is_onlocal": "0" 22 | }, 23 | { 24 | "url": "http://public-alicliimg.clewm.net/dwagimg/863b4684432e71adff1d76902c0a0b93.png", 25 | "size": "286927", 26 | "width": "1410", 27 | "height": "1410", 28 | "is_onlocal": "0" 29 | }, 30 | { 31 | "url": "http://public-alicliimg.clewm.net/dwagimg/f39dc16eed18e74eb211df1a014881a6.png", 32 | "size": "265634", 33 | "width": "1410", 34 | "height": "1410", 35 | "is_onlocal": "0" 36 | }, 37 | { 38 | "url": "http://public-alicliimg.clewm.net/dwagimg/6eccbe1c907948f8238d17161913b92f.png", 39 | "size": "362623", 40 | "width": "1410", 41 | "height": "1410", 42 | "is_onlocal": "0" 43 | }, 44 | { 45 | "url": "http://public-alicliimg.clewm.net/dwagimg/56d57dbb54e98b462c46f37059aba62c.png", 46 | "size": "306385", 47 | "width": "1410", 48 | "height": "1410", 49 | "is_onlocal": "0" 50 | }, 51 | { 52 | "url": "http://public-alicliimg.clewm.net/dwagimg/2fd608a24d2b0f3b4c17e1d580f71553.png", 53 | "size": "362321", 54 | "width": "1410", 55 | "height": "1410", 56 | "is_onlocal": "0" 57 | }, 58 | { 59 | "url": "http://public-alicliimg.clewm.net/dwagimg/086a2926612a427c788fae08459b8d55.png", 60 | "size": "340887", 61 | "width": "1410", 62 | "height": "1410", 63 | "is_onlocal": "0" 64 | }, 65 | { 66 | "url": "https://uploader.shimo.im/f/ZLYlotu9LwraXaEu.png!original", 67 | "size": "1032445", 68 | "width": "1410", 69 | "height": "2508", 70 | "is_onlocal": "0" 71 | } 72 | ]; 73 | 74 | const App = () => { 75 | 76 | const [visible, setVisible] = useState(false); 77 | 78 | const ChangeVisible = () => { 79 | setVisible(true); 80 | } 81 | 82 | return ( 83 |
84 | 88 | 点击放大 89 | 90 | { 96 | setVisible(false); 97 | } 98 | } 99 | /> 100 |
101 | ); 102 | } 103 | 104 | render(, document.getElementById('root')); -------------------------------------------------------------------------------- /example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /example/src/index.less: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | margin-top: 36px; 4 | 5 | .icon { 6 | width: 100px; 7 | height: 100px; 8 | display: inline-block; 9 | } 10 | 11 | .demo { 12 | width: 110px; 13 | height: 36px; 14 | border-radius: 4px; 15 | display: inline-block; 16 | border: 1px solid #4caf50; 17 | text-align: center; 18 | line-height: 36px; 19 | color: #4caf50; 20 | background-color: #fff; 21 | cursor: pointer; 22 | 23 | &:hover { 24 | background-color: #4caf50; 25 | color: #fff; 26 | } 27 | 28 | } 29 | } -------------------------------------------------------------------------------- /example/src/index.less.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexLessModule { 2 | export interface IIndexLess { 3 | App: string; 4 | app: string; 5 | demo: string; 6 | icon: string; 7 | } 8 | } 9 | 10 | declare const IndexLessModule: IndexLessModule.IIndexLess & { 11 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 12 | locals: IndexLessModule.IIndexLess; 13 | }; 14 | 15 | export = IndexLessModule; 16 | -------------------------------------------------------------------------------- /lib/58640177ac8479591e07836acf38b280.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrell0904/darrell-photo-gallery/f008fb722b9414966ac9c9c357f34e9561145291/lib/58640177ac8479591e07836acf38b280.ttf -------------------------------------------------------------------------------- /lib/5e7cb895895e7d9a1f42692c25f0f89e.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /lib/6ca395623c66716ea37499830cca838e.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrell0904/darrell-photo-gallery/f008fb722b9414966ac9c9c357f34e9561145291/lib/6ca395623c66716ea37499830cca838e.eot -------------------------------------------------------------------------------- /lib/d3af303528816fafe00926a29dba3a50.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrell0904/darrell-photo-gallery/f008fb722b9414966ac9c9c357f34e9561145291/lib/d3af303528816fafe00926a29dba3a50.woff -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | !function(t,i){if("object"==typeof exports&&"object"==typeof module)module.exports=i(require("react"));else if("function"==typeof define&&define.amd)define(["react"],i);else{var e="object"==typeof exports?i(require("react")):i(t.React);for(var n in e)("object"==typeof exports?exports:t)[n]=e[n]}}(window,(function(t){return function(t){var i={};function e(n){if(i[n])return i[n].exports;var a=i[n]={i:n,l:!1,exports:{}};return t[n].call(a.exports,a,a.exports,e),a.l=!0,a.exports}return e.m=t,e.c=i,e.d=function(t,i,n){e.o(t,i)||Object.defineProperty(t,i,{enumerable:!0,get:n})},e.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},e.t=function(t,i){if(1&i&&(t=e(t)),8&i)return t;if(4&i&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(e.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&i&&"string"!=typeof t)for(var a in t)e.d(n,a,function(i){return t[i]}.bind(null,a));return n},e.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(i,"a",i),i},e.o=function(t,i){return Object.prototype.hasOwnProperty.call(t,i)},e.p="",e(e.s=9)}([function(i,e){i.exports=t},function(t,i,e){t.exports={iconfont:"iconfont_3eB5u","icon-552cc1b6033d0":"icon-552cc1b6033d0_2YVzp",icon552Cc1B6033D0:"icon-552cc1b6033d0_2YVzp","icon-suoxiao":"icon-suoxiao_3WWPh",iconSuoxiao:"icon-suoxiao_3WWPh","icon-left":"icon-left_3JOFk",iconLeft:"icon-left_3JOFk","icon-right":"icon-right_YLzYC",iconRight:"icon-right_YLzYC","icon-top":"icon-top_1S80M",iconTop:"icon-top_1S80M","icon-Plus":"icon-Plus_3e8I4",iconPlus:"icon-Plus_3e8I4","icon-jian":"icon-jian_29qE4",iconJian:"icon-jian_29qE4","icon-fangda":"icon-fangda_2gFXV",iconFangda:"icon-fangda_2gFXV","icon-down":"icon-down_1j-VT",iconDown:"icon-down_1j-VT","icon-shuangyoujiantou-":"icon-shuangyoujiantou-_qkRf6",iconShuangyoujiantou:"icon-shuangyoujiantou-_qkRf6","icon-shuangzuojiantou-":"icon-shuangzuojiantou-__rsYI",iconShuangzuojiantou:"icon-shuangzuojiantou-__rsYI","icon-xiazai":"icon-xiazai_29ZT2",iconXiazai:"icon-xiazai_29ZT2","icon-ellipsis2":"icon-ellipsis2_fhcnb",iconEllipsis2:"icon-ellipsis2_fhcnb",footer:"footer_16G8g",toolbarIndex:"toolbarIndex_2xofB",toolbarIndexNumber:"toolbarIndexNumber_AN8FI",toolBarActions:"toolBarActions_1J3h0",toolBarItems:"toolBarItems_3_ie9",icon:"icon_3YMu_"}},function(t,i,e){var n; 2 | /*! 3 | Copyright (c) 2017 Jed Watson. 4 | Licensed under the MIT License (MIT), see 5 | http://jedwatson.github.io/classnames 6 | */!function(){"use strict";var e={}.hasOwnProperty;function a(){for(var t=[],i=0;i=i&&h<=.55&&(l=(a-(f=(A=i)*e/i))/2),m=u-A/2),g<1&&(l=(a-(f=(A=n-.05*n*2)*e/i))/2,a/n<=g&&(A=(f=a-.05*a*2)*i/e,l=.05*a),m=u-A/2),{imgLeft:m,imgTop:l,ImgWidth:A,ImgHeight:f}},p=function(t,i){if(!t)return 6;var e=function(t,i){for(var e=0,n=t.length-1;e<=n;){var a=Math.floor((n+e)/2);if(n-e<=1)break;var o=t[a];if(o===i)return o;o>i?n=a:e=a}var r=t[e],s=t[n];return s-i>i-r?r:s}(t,i);return t.indexOf(e)},I=function(){function t(t,i){this.canUseCanvas=!0,this.LongImgTop=10,this.sidebarWidth=120,this.footerHeight=50,this.winWidth=window.innerWidth,this.winHeight=window.innerHeight,this.curPos={x:0,y:0},this.curScaleIndex=6,this.fixScreenSize=6,this.EachSizeWidthArray=[],this.isDoCallback=!1;var e=i.imgUrl,n=i.winWidth,a=i.winHeight,o=i.canUseCanvas;this.el=t,this.imgUrl=e,this.winWidth=n,this.winHeight=a,this.canUseCanvas=o,this.options=i,this.canUseCanvas?(this.context=this.el.getContext("2d"),this.canvasInit()):this.imageInit()}return t.prototype.imageInit=function(){this.cWidth=this.winWidth-this.sidebarWidth,this.cHeight=this.winHeight-this.footerHeight,this.image&&this.setImgStyle("init"),this.loadimg(this.setImgStyle.bind(this,"init"))},t.prototype.setImgStyle=function(t){var i=this.image;if(i){if("init"==t){var e=d({naturalWidth:i.width,naturalHeight:i.height,wrapperWidth:this.cWidth,wrapperHeight:this.cHeight,curImgWidth:this.cImgWidth||i.width,curImgHeight:this.cImgHeight||i.height});this.imgLeft=e.imgLeft,this.imgTop=e.imgTop,this.cImgWidth=e.ImgWidth,this.cImgHeight=e.ImgHeight,this.LongImgLeft=this.imgLeft,this.LongImgTop=this.imgTop,this.setCurScaleIndex()}this.el.style.width=this.cImgWidth+"px",this.el.style.height=this.cImgHeight+"px",this.el.style.top=this.imgTop+"px",this.el.style.left=this.imgLeft+"px"}},t.prototype.canvasInit=function(){this.cWidth=this.winWidth-this.sidebarWidth,this.cHeight=this.winHeight-this.footerHeight,this.el.width=this.cWidth,this.el.height=this.cHeight,this.image?this.drawImg("init"):this.loadimg(this.drawImg.bind(this,"init"))},t.prototype.drawImg=function(t){var i=this,e=this.image,n=this.context;if(e){if("init"==t){var a=d({naturalWidth:e.width,naturalHeight:e.height,wrapperWidth:this.cWidth,wrapperHeight:this.cHeight,curImgWidth:this.cImgWidth||e.width,curImgHeight:this.cImgHeight||e.height,curImgTop:this.imgTop||0,curImgLeft:this.imgLeft||0,winWidth:this.winWidth});this.imgLeft=a.imgLeft,this.imgTop=a.imgTop,this.cImgWidth=a.ImgWidth,this.cImgHeight=a.ImgHeight,this.LongImgLeft=this.imgLeft,this.LongImgTop=this.imgTop,this.setCurScaleIndex()}this.cacheCanvas||(this.cacheCanvas=document.createElement("canvas")),this.cacheCanvas.width=this.cWidth,this.cacheCanvas.height=this.cHeight,this.cacheCanvas.getContext("2d").drawImage(e,this.imgLeft,this.imgTop,this.cImgWidth,this.cImgHeight),requestAnimationFrame((function(){i.clearLastCanvas(n),n.drawImage(i.cacheCanvas,0,0)})),this.options.loadingComplete&&!this.isDoCallback&&(this.isDoCallback=!0,this.options.loadingComplete(this))}},t.prototype.setCurScaleIndex=function(){var t=this.cImgWidth||this.image.width,i=this.EachSizeWidthArray,e=p(i,t);this.curScaleIndex=e},t.prototype.changeCurSizeIndex=function(t){var i=t;i>12&&(i=12),i<0&&(i=0);var e,n=this.cWidth,a=this.cHeight,o=this.curScaleIndex,r=this.EachSizeWidthArray;e=r[i]/r[o],this.cImgHeight=this.cImgHeight*e,this.cImgWidth=this.cImgWidth*e,this.imgTop=a/2-(a/2-this.imgTop)*e,this.curScaleIndex=i,this.cImgHeighte&&(this.LongImgTop=this.LongImgTop-t.deltaY,t.deltaY>0?-this.LongImgTop>this.cImgHeight+10-window.innerHeight+this.footerHeight&&(this.LongImgTop=-(this.cImgHeight+10-window.innerHeight+this.footerHeight)):this.LongImgTop>10&&(this.LongImgTop=10)),this.cImgWidth>i&&(this.LongImgLeft=this.LongImgLeft-t.deltaX,t.deltaX>0?-this.LongImgLeft>this.cImgWidth+10-window.innerWidth+this.sidebarWidth&&(this.LongImgLeft=-(this.cImgWidth+10-window.innerWidth+this.sidebarWidth)):this.LongImgLeft>10&&(this.LongImgLeft=10)),this.imgTop=this.LongImgTop,this.imgLeft=this.LongImgLeft,this.canUseCanvas?this.drawImg():this.setImgStyle())}},t.prototype.MoveCanvas=function(t,i){if(t){var e=this.cWidth,n=this.cHeight;if(this.cImgHeightthis.cHeight&&(this.LongImgTop=this.LongImgTop+(o-this.curPos.y),o-s>0?this.LongImgTop>=10&&(this.LongImgTop=10):-this.LongImgTop>this.cImgHeight+10-window.innerHeight+this.footerHeight&&(this.LongImgTop=-(this.cImgHeight+10-window.innerHeight+this.footerHeight))),this.cImgWidth>this.cWidth&&(this.LongImgLeft=this.LongImgLeft+(a-this.curPos.x),a-r>0?this.LongImgLeft>=10&&(this.LongImgLeft=10):-this.LongImgLeft>this.cImgWidth+10-window.innerWidth+this.sidebarWidth&&(this.LongImgLeft=-(this.cImgWidth+10-window.innerWidth+this.sidebarWidth)),this.imgLeft=this.LongImgLeft),this.curPos.x=a,this.curPos.y=o,this.imgTop=this.LongImgTop,this.imgLeft=this.LongImgLeft,this.canUseCanvas?this.drawImg():this.setImgStyle()}},t.prototype.loadimg=function(t){var i=new Image;i.src=this.imgUrl;var e=this;i.onload=function(){e.image=this,e.setEachSizeArr(),t()}},t.prototype.setEachSizeArr=function(){var t,i,e,n,a,o=this.image,r=(t={naturalWidth:o.width,naturalHeight:o.height},i=t.naturalWidth,n=(4*i-i)/6,a=(i-(e=i/10))/6,A(Array.from({length:6},(function(t,i){return e+i*a})),Array.from({length:7},(function(t,e){return i+e*n}))));this.EachSizeWidthArray=r;var s=function(t,i){for(var e=t.naturalWidth,n=t.naturalHeight,a=t.wrapperWidth,o=t.wrapperHeight,r=6,s=i.length-1;s>=0;s--){var c=i[s];if(c12&&(t=12),S(t)}),[]),G=Object(n.useCallback)((function(t){t<0&&(t=0),t>12&&(t=12),I(t)}),[]),K=Object(n.useCallback)((function(t){F(t)}),[]),T=Object(n.useCallback)((function(t){A(t)}),[]),_=Object(n.useCallback)((function(t){N(t)}),[]),O=null;return O=o?a.a.createElement(W,{WinSize:j,WheelUpdate:Q,MouseDown:J,MouseMove:D,MouseUp:z,Click:U,canUseCanvas:o,curSize:p,imgUrl:m,imgLoading:q,setFixScreenSize:k,setCurSize:G,setImgLoading:K}):a.a.createElement(y,{imgUrl:"https://images-cdn.shimo.im/lfHCtJgv20E3RfkO/161.png__original",WinSize:j,canUseCanvas:o,WheelUpdate:Q,MouseDown:J,MouseMove:D,MouseUp:z,Click:U}),a.a.createElement("div",{className:c()(E.modalWrapper,(i={},i[E.showImgGallery]=g,i))},a.a.createElement("div",{className:E.contentWrapper},a.a.createElement("div",{className:E.imgwrapper},a.a.createElement("img",{src:r,alt:"loading",className:c()(E.loadingImg,(e={},e[E.imgLoding]=q,e))}),O),a.a.createElement(f,{imgData:s,curImgUrl:m,setImgUrl:T,setCurrentImgIndex:_,setImgLoading:K}),a.a.createElement(u,{curSize:p,fixScreenSize:v,imgsLens:s.length,currentImgIndex:H,setCurSize:G})))}}]).default})); -------------------------------------------------------------------------------- /lib/main.min.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "iconfont"; 3 | src: url(6ca395623c66716ea37499830cca838e.eot); 4 | /* IE9 */ 5 | src: url(6ca395623c66716ea37499830cca838e.eot#iefix) format('embedded-opentype'), url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), url(d3af303528816fafe00926a29dba3a50.woff) format('woff'), url(58640177ac8479591e07836acf38b280.ttf) format('truetype'), url(5e7cb895895e7d9a1f42692c25f0f89e.svg#iconfont) format('svg'); 6 | /* iOS 4.1- */ 7 | } 8 | .iconfont_3eB5u { 9 | font-family: "iconfont" !important; 10 | font-size: 16px; 11 | font-style: normal; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | .icon-552cc1b6033d0_2YVzp:before { 16 | content: "\e601"; 17 | } 18 | .icon-suoxiao_3WWPh:before { 19 | content: "\e616"; 20 | } 21 | .icon-left_3JOFk:before { 22 | content: "\e609"; 23 | } 24 | .icon-right_YLzYC:before { 25 | content: "\e61b"; 26 | } 27 | .icon-top_1S80M:before { 28 | content: "\e626"; 29 | } 30 | .icon-Plus_3e8I4:before { 31 | content: "\e60b"; 32 | } 33 | .icon-jian_29qE4:before { 34 | content: "\e620"; 35 | } 36 | .icon-fangda_2gFXV:before { 37 | content: "\e826"; 38 | } 39 | .icon-down_1j-VT:before { 40 | content: "\e61a"; 41 | } 42 | .icon-shuangyoujiantou-_qkRf6:before { 43 | content: "\e62a"; 44 | } 45 | .icon-shuangzuojiantou-__rsYI:before { 46 | content: "\e62b"; 47 | } 48 | .icon-xiazai_29ZT2:before { 49 | content: "\e7de"; 50 | } 51 | .icon-ellipsis2_fhcnb:before { 52 | content: "\e701"; 53 | } 54 | .footer_16G8g { 55 | position: absolute; 56 | right: 0; 57 | bottom: 0; 58 | left: 0; 59 | width: calc(100% - 120px); 60 | height: 50px; 61 | margin-left: 0; 62 | text-align: left; 63 | line-height: 50px; 64 | background-color: rgba(34, 34, 34, 0.88); 65 | } 66 | .footer_16G8g .toolbarIndex_2xofB { 67 | font-size: 16px; 68 | color: #fff; 69 | } 70 | .footer_16G8g .toolbarIndex_2xofB .toolbarIndexNumber_AN8FI { 71 | margin-left: 40px; 72 | } 73 | .footer_16G8g .toolBarActions_1J3h0 { 74 | position: absolute; 75 | right: 0; 76 | bottom: 0; 77 | height: 50px; 78 | } 79 | .footer_16G8g .toolBarActions_1J3h0 .toolBarItems_3_ie9 { 80 | position: relative; 81 | display: inline-block; 82 | width: 40px; 83 | height: 50px; 84 | text-align: center; 85 | font-size: 24px; 86 | line-height: 24px; 87 | padding: 0 13px; 88 | text-decoration: none; 89 | color: #fff; 90 | opacity: 0.7; 91 | } 92 | .footer_16G8g .toolBarActions_1J3h0 .toolBarItems_3_ie9 .icon_3YMu_ { 93 | display: block; 94 | font-size: 24px; 95 | width: 24px; 96 | height: 22px; 97 | margin-left: 8px; 98 | margin-top: 14px; 99 | pointer-events: none; 100 | } 101 | .footer_16G8g .toolBarActions_1J3h0 .toolBarItems_3_ie9:hover { 102 | background-color: #000; 103 | opacity: 1; 104 | } 105 | 106 | .sidebar_lmkyG { 107 | position: absolute; 108 | top: 0; 109 | right: 0; 110 | bottom: 0; 111 | left: auto; 112 | width: 120px; 113 | height: auto; 114 | overflow-y: auto; 115 | overflow-x: hidden; 116 | background-color: #0a0a0a; 117 | box-shadow: 0 0 18px rgba(0, 0, 0, 0.6); 118 | } 119 | .sidebar_lmkyG .imgItem_3wBAW { 120 | margin: 10px; 121 | width: 90px; 122 | height: 60px; 123 | overflow: hidden; 124 | border: 1px solid hsla(0, 0%, 100%, 0.3); 125 | cursor: pointer; 126 | } 127 | .sidebar_lmkyG .imgItem_3wBAW .imgSpan_2zR0T { 128 | opacity: 0.6; 129 | width: 100%; 130 | height: 100%; 131 | display: block; 132 | margin: auto; 133 | border: 0; 134 | } 135 | .sidebar_lmkyG .imgItem_3wBAW .imgSpan_2zR0T img { 136 | width: 100%; 137 | height: 100%; 138 | vertical-align: middle; 139 | object-fit: contain; 140 | } 141 | .sidebar_lmkyG .imgItem_3wBAW:hover .imgSpan_2zR0T { 142 | opacity: 1; 143 | } 144 | 145 | .isImgLoading_2SDum { 146 | display: none; 147 | } 148 | 149 | .imgBox_3FEpz { 150 | position: absolute; 151 | right: 0; 152 | bottom: 0; 153 | border: 0; 154 | background: #fff; 155 | box-shadow: 0 0 28px rgba(0, 0, 0, 0.2); 156 | margin: 0px; 157 | max-width: none; 158 | max-height: none; 159 | } 160 | .isVisiblity_3fIwM { 161 | opacity: 0; 162 | } 163 | 164 | @font-face { 165 | font-family: "iconfont"; 166 | src: url(6ca395623c66716ea37499830cca838e.eot); 167 | /* IE9 */ 168 | src: url(6ca395623c66716ea37499830cca838e.eot#iefix) format('embedded-opentype'), url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), url(d3af303528816fafe00926a29dba3a50.woff) format('woff'), url(58640177ac8479591e07836acf38b280.ttf) format('truetype'), url(5e7cb895895e7d9a1f42692c25f0f89e.svg#iconfont) format('svg'); 169 | /* iOS 4.1- */ 170 | } 171 | .iconfont { 172 | font-family: "iconfont" !important; 173 | font-size: 16px; 174 | font-style: normal; 175 | -webkit-font-smoothing: antialiased; 176 | -moz-osx-font-smoothing: grayscale; 177 | } 178 | .icon-552cc1b6033d0:before { 179 | content: "\e601"; 180 | } 181 | .icon-suoxiao:before { 182 | content: "\e616"; 183 | } 184 | .icon-left:before { 185 | content: "\e609"; 186 | } 187 | .icon-right:before { 188 | content: "\e61b"; 189 | } 190 | .icon-top:before { 191 | content: "\e626"; 192 | } 193 | .icon-Plus:before { 194 | content: "\e60b"; 195 | } 196 | .icon-jian:before { 197 | content: "\e620"; 198 | } 199 | .icon-fangda:before { 200 | content: "\e826"; 201 | } 202 | .icon-down:before { 203 | content: "\e61a"; 204 | } 205 | .icon-shuangyoujiantou-:before { 206 | content: "\e62a"; 207 | } 208 | .icon-shuangzuojiantou-:before { 209 | content: "\e62b"; 210 | } 211 | .icon-xiazai:before { 212 | content: "\e7de"; 213 | } 214 | .icon-ellipsis2:before { 215 | content: "\e701"; 216 | } 217 | 218 | .modalWrapper_1e0sC { 219 | position: fixed; 220 | top: 0; 221 | left: 0; 222 | z-index: 10000; 223 | width: 100%; 224 | height: 100%; 225 | display: block; 226 | transition: background-color 0.2s ease-out; 227 | background-color: rgba(0, 0, 0, 0.65); 228 | display: none; 229 | } 230 | .modalWrapper_1e0sC.showImgGallery_iqmiu { 231 | display: block; 232 | } 233 | .modalWrapper_1e0sC .contentWrapper_2dutM { 234 | height: 100%; 235 | } 236 | .modalWrapper_1e0sC .contentWrapper_2dutM .imgwrapper_1ZByt { 237 | position: absolute; 238 | width: calc(100% - 120px); 239 | height: 100%; 240 | top: 0; 241 | right: 0; 242 | bottom: 0; 243 | left: 0; 244 | right: auto; 245 | margin: auto; 246 | cursor: pointer; 247 | transition: all 0.2s ease-out; 248 | } 249 | .modalWrapper_1e0sC .contentWrapper_2dutM .imgwrapper_1ZByt .loadingImg_lD7yt { 250 | position: absolute; 251 | top: calc(50% - 50px); 252 | left: 50%; 253 | display: none; 254 | } 255 | .modalWrapper_1e0sC .contentWrapper_2dutM .imgwrapper_1ZByt .loadingImg_lD7yt.imgLoding_2MGfb { 256 | display: inline-block; 257 | } 258 | .modalWrapper_1e0sC .contentWrapper_2dutM .imgwrapper_1ZByt canvas { 259 | width: 100%; 260 | height: calc(100% - 50px); 261 | } 262 | .modalWrapper_1e0sC .contentWrapper_2dutM .sidebar_3xS_J { 263 | position: absolute; 264 | top: 0; 265 | right: 0; 266 | bottom: 0; 267 | left: auto; 268 | width: 120px; 269 | height: auto; 270 | overflow-y: auto; 271 | overflow-x: hidden; 272 | background-color: #0a0a0a; 273 | box-shadow: 0 0 18px rgba(0, 0, 0, 0.6); 274 | } 275 | .modalWrapper_1e0sC .contentWrapper_2dutM .footer_1oxoM { 276 | position: absolute; 277 | right: 0; 278 | bottom: 0; 279 | left: 0; 280 | width: auto; 281 | height: 50px; 282 | margin-left: 0; 283 | text-align: left; 284 | line-height: 50px; 285 | background-color: rgba(34, 34, 34, 0.88); 286 | } 287 | 288 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "darrell-photo-gallery", 3 | "version": "1.1.0", 4 | "description": "一个基于 react hoos 开发的 仿石墨的 图片放大插件", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --config config/webpack.dev.config.js", 8 | "build": "webpack --config config/webpack.prod.config.js", 9 | "pub": "npm run build && npm publish" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/darrell0904/darrell-photo-gallery.git" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/darrell0904/darrell-photo-gallery/issues" 19 | }, 20 | "homepage": "https://github.com/darrell0904/darrell-photo-gallery#readme", 21 | "devDependencies": { 22 | "@babel/cli": "^7.8.3", 23 | "@babel/core": "^7.8.3", 24 | "@babel/preset-env": "^7.8.3", 25 | "@babel/preset-react": "^7.8.3", 26 | "@babel/preset-typescript": "^7.8.3", 27 | "@teamsupercell/typings-for-css-modules-loader": "^2.1.0", 28 | "@types/classnames": "^2.2.9", 29 | "@types/react": "^16.9.19", 30 | "@types/react-dom": "^16.9.5", 31 | "awesome-typescript-loader": "^5.2.1", 32 | "babel-loader": "^8.0.6", 33 | "classnames": "^2.2.6", 34 | "clean-webpack-plugin": "^3.0.0", 35 | "css-loader": "^3.4.2", 36 | "file-loader": "^2.0.0", 37 | "html-webpack-plugin": "^3.2.0", 38 | "less": "^3.10.3", 39 | "less-loader": "^5.0.0", 40 | "mini-css-extract-plugin": "^0.9.0", 41 | "react": "^16.12.0", 42 | "react-dom": "^16.12.0", 43 | "style-loader": "^1.1.3", 44 | "ts-loader": "^6.2.1", 45 | "tslint": "^6.0.0", 46 | "tslint-config-prettier": "^1.18.0", 47 | "tslint-config-standard": "^9.0.0", 48 | "tslint-loader": "^3.5.4", 49 | "typed-css-modules": "^0.6.3", 50 | "typed-css-modules-loader": "0.0.18", 51 | "typescript": "^3.7.5", 52 | "typings-for-css-modules-loader": "^1.7.0", 53 | "url-loader": "^3.0.0", 54 | "webpack": "^4.41.5", 55 | "webpack-cli": "^3.3.10", 56 | "webpack-dev-server": "^3.10.1", 57 | "webpack-merge": "^4.2.2" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/components/photoGallery/canvas.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getBoundingClientRect, 3 | getEachSizeWidthArray, 4 | getFixScreenIndex, 5 | getCurImgIndex, 6 | } from '@utils/img'; 7 | 8 | interface CanvasOptions { 9 | imgUrl: string; // 图片地址 10 | winWidth: number; // 屏幕宽度 11 | winHeight: number; // 屏幕高度 12 | canUseCanvas: boolean; // 是否可以使用 canUseCanvas 13 | loadingComplete?(instance: any): void; // loading 14 | } 15 | 16 | interface curPos { 17 | x: number, 18 | y: number, 19 | } 20 | 21 | const LONG_IMG_TOP: number = 10; // 长图距离顶部和底部的距离 22 | const LONG_IMG_Left: number = 10; // 长图距离顶部和底部的距离 23 | 24 | 25 | class ImgToCanvas { 26 | private options : CanvasOptions // 所有配置 27 | private el : any; // canvas dom 28 | private cacheCanvas : any; 29 | 30 | private canUseCanvas: boolean = true; // 是否能使用canvas 31 | 32 | private context : any; 33 | 34 | private image: any; // img dom 35 | private imgUrl: string; // img url 36 | 37 | private imgTop: number; // img 的 top 38 | private imgLeft: number; // img 的 left 39 | 40 | private LongImgTop: number = LONG_IMG_TOP; 41 | private LongImgLeft: number; 42 | 43 | private sidebarWidth: number = 120; // 侧边栏的宽度 44 | private footerHeight: number = 50; // 底部栏的高度 45 | 46 | private cImgWidth: number; // canvas.width 47 | private cImgHeight: number; // canvas.height 48 | 49 | private winWidth: number = window.innerWidth; // window.width 50 | private winHeight: number = window.innerHeight; // window.height 51 | 52 | private cWidth: number; // canvas.width 53 | private cHeight: number; // canvas.height 54 | 55 | private curPos: curPos = { // 记录鼠标按下时的坐标 56 | x: 0, 57 | y: 0, 58 | } 59 | 60 | private curScaleIndex: number = 6; // 现在的尺寸在哪一个地方 61 | public fixScreenSize: number = 6; // 适应屏幕的 size 62 | 63 | private EachSizeWidthArray: number[] = []; // 每个阶段的宽度尺寸 64 | 65 | private isDoCallback: boolean = false; // 图片是否已经加载 66 | 67 | // 初始化函数(输入的是canvas) 68 | constructor (selector: any, options: CanvasOptions) { 69 | 70 | const { 71 | imgUrl, 72 | winWidth, 73 | winHeight, 74 | canUseCanvas, 75 | } = options; 76 | 77 | // 设置 78 | this.el = selector; 79 | this.imgUrl = imgUrl; 80 | this.winWidth = winWidth; 81 | this.winHeight = winHeight; 82 | this.canUseCanvas = canUseCanvas; 83 | 84 | this.options = options; 85 | 86 | if (this.canUseCanvas) { 87 | this.context = this.el.getContext('2d'); 88 | this.canvasInit(); 89 | } else { 90 | this.imageInit(); 91 | } 92 | } 93 | 94 | /** 95 | * 图片初始化 96 | */ 97 | private imageInit () { 98 | this.cWidth = this.winWidth - this.sidebarWidth; 99 | this.cHeight = this.winHeight - this.footerHeight; 100 | 101 | if (this.image) { 102 | this.setImgStyle('init'); 103 | } 104 | 105 | this.loadimg(this.setImgStyle.bind(this, 'init')); 106 | } 107 | 108 | /** 109 | * 设置图片的样式 110 | */ 111 | setImgStyle (type?: string) { 112 | const image = this.image; 113 | 114 | if (!image) return; 115 | 116 | if (type == "init") { 117 | const imgRect = getBoundingClientRect({ 118 | naturalWidth: image.width, 119 | naturalHeight: image.height, 120 | wrapperWidth: this.cWidth, 121 | wrapperHeight: this.cHeight, 122 | curImgWidth: this.cImgWidth || image.width, 123 | curImgHeight: this.cImgHeight || image.height, 124 | }); 125 | 126 | this.imgLeft = imgRect.imgLeft; 127 | this.imgTop = imgRect.imgTop; 128 | this.cImgWidth = imgRect.ImgWidth; 129 | this.cImgHeight = imgRect.ImgHeight; 130 | 131 | this.LongImgLeft = this.imgLeft; 132 | this.LongImgTop = this.imgTop; 133 | 134 | this.setCurScaleIndex(); 135 | } 136 | 137 | this.el.style.width = `${this.cImgWidth}px`; 138 | this.el.style.height = `${this.cImgHeight}px`; 139 | this.el.style.top = `${this.imgTop}px`; 140 | this.el.style.left = `${this.imgLeft}px`; 141 | } 142 | 143 | private canvasInit () { 144 | this.cWidth = this.winWidth - this.sidebarWidth; 145 | this.cHeight = this.winHeight - this.footerHeight; 146 | 147 | this.el.width = this.cWidth; 148 | this.el.height = this.cHeight; 149 | 150 | if (this.image) { 151 | this.drawImg('init'); 152 | return; 153 | } 154 | 155 | this.loadimg(this.drawImg.bind(this, 'init')); 156 | } 157 | 158 | private drawImg (type?: string) { 159 | const image = this.image; 160 | const context = this.context; 161 | 162 | if (!image) return; 163 | 164 | if (type == "init") { 165 | const imgRect = getBoundingClientRect({ 166 | naturalWidth: image.width, 167 | naturalHeight: image.height, 168 | wrapperWidth: this.cWidth, 169 | wrapperHeight: this.cHeight, 170 | curImgWidth: this.cImgWidth || image.width, 171 | curImgHeight: this.cImgHeight || image.height, 172 | curImgTop: this.imgTop || 0, 173 | curImgLeft: this.imgLeft || 0, 174 | winWidth: this.winWidth, 175 | }); 176 | 177 | this.imgLeft = imgRect.imgLeft; 178 | this.imgTop = imgRect.imgTop; 179 | this.cImgWidth = imgRect.ImgWidth; 180 | this.cImgHeight = imgRect.ImgHeight; 181 | 182 | this.LongImgLeft = this.imgLeft; 183 | this.LongImgTop = this.imgTop; 184 | 185 | this.setCurScaleIndex(); 186 | } 187 | 188 | if (!this.cacheCanvas) { 189 | this.cacheCanvas = document.createElement("canvas"); 190 | } 191 | 192 | this.cacheCanvas.width = this.cWidth; 193 | this.cacheCanvas.height = this.cHeight; 194 | const tempCtx = this.cacheCanvas.getContext('2d')!; 195 | 196 | tempCtx.drawImage(image, this.imgLeft, this.imgTop, this.cImgWidth, this.cImgHeight); 197 | 198 | requestAnimationFrame(() => { 199 | this.clearLastCanvas(context); 200 | context.drawImage(this.cacheCanvas, 0, 0); 201 | }) 202 | 203 | if (this.options.loadingComplete && !this.isDoCallback) { 204 | this.isDoCallback = true; 205 | this.options.loadingComplete(this); 206 | } 207 | } 208 | 209 | /** 210 | * 设置当前 EachSizeWidthArray 的索引,用于 放大缩小 211 | */ 212 | private setCurScaleIndex() { 213 | const cImgWidth = this.cImgWidth || this.image.width; 214 | 215 | const EachSizeWidthArray = this.EachSizeWidthArray; 216 | 217 | const curScaleIndex = getCurImgIndex(EachSizeWidthArray, cImgWidth); 218 | 219 | this.curScaleIndex = curScaleIndex; 220 | } 221 | 222 | /** 223 | * 修改当前 图片大小数组中的 索引 224 | * @param curSizeIndex : 225 | */ 226 | public changeCurSizeIndex(curSizeIndex: number) { 227 | let curScaleIndex = curSizeIndex; 228 | 229 | if (curScaleIndex > 12) curScaleIndex = 12; 230 | if (curScaleIndex < 0) curScaleIndex = 0; 231 | 232 | const cWidth = this.cWidth; 233 | const cHeight = this.cHeight; 234 | 235 | // const imageRadio = this.imageRadio; 236 | 237 | const prevScaleTimes = this.curScaleIndex; 238 | const EachSizeWidthArray = this.EachSizeWidthArray; 239 | 240 | let scaleRadio = 1; 241 | 242 | scaleRadio = EachSizeWidthArray[curScaleIndex] / EachSizeWidthArray[prevScaleTimes]; 243 | 244 | this.cImgHeight = this.cImgHeight * scaleRadio; 245 | this.cImgWidth = this.cImgWidth * scaleRadio; 246 | 247 | this.imgTop = cHeight / 2 - (cHeight / 2 - this.imgTop) * scaleRadio; 248 | this.curScaleIndex = curScaleIndex; 249 | 250 | if (this.cImgHeight < cHeight && this.cImgWidth < cWidth) { 251 | this.imgTop = (cHeight - this.cImgHeight) / 2; 252 | } 253 | 254 | this.imgLeft = cWidth / 2 - this.cImgWidth / 2; 255 | 256 | this.LongImgTop = this.imgTop; 257 | this.LongImgLeft = this.imgLeft; 258 | 259 | if (this.canUseCanvas) { 260 | this.drawImg(); 261 | } else { 262 | this.setImgStyle(); 263 | } 264 | } 265 | 266 | /** 267 | * 清除画布 268 | * @param ctx 269 | */ 270 | private clearLastCanvas (ctx: any) { 271 | ctx.clearRect(0, 0, this.cWidth, this.cHeight); 272 | } 273 | 274 | /** 275 | * 滚轮事件 276 | * @param e wheel 的事件参数 277 | */ 278 | public WheelUpdate(e: any) { 279 | const image = this.image; 280 | 281 | if (!image) { 282 | return; 283 | } 284 | 285 | const cWidth = this.cWidth; 286 | const cHeight = this.cHeight; 287 | 288 | if (this.cImgHeight < cHeight && this.cImgWidth < cWidth) { 289 | return; 290 | } 291 | 292 | if (this.cImgHeight > cHeight) { 293 | this.LongImgTop = this.LongImgTop - e.deltaY; 294 | 295 | if (e.deltaY > 0) { // 向下 296 | if ((-this.LongImgTop) > this.cImgHeight + LONG_IMG_TOP - window.innerHeight + this.footerHeight) { // 这个 50 为 缓冲 297 | this.LongImgTop = -(this.cImgHeight + LONG_IMG_TOP - window.innerHeight + this.footerHeight); 298 | } 299 | } else { 300 | if (this.LongImgTop > LONG_IMG_TOP) { 301 | this.LongImgTop = LONG_IMG_TOP; 302 | } 303 | } 304 | } 305 | 306 | if (this.cImgWidth > cWidth) { 307 | this.LongImgLeft = this.LongImgLeft - e.deltaX; 308 | 309 | if (e.deltaX > 0) { 310 | if ((-this.LongImgLeft) > this.cImgWidth + LONG_IMG_Left - window.innerWidth + this.sidebarWidth) { 311 | this.LongImgLeft = -(this.cImgWidth + LONG_IMG_Left - window.innerWidth + this.sidebarWidth); 312 | } 313 | } else { 314 | if (this.LongImgLeft > LONG_IMG_Left) { 315 | this.LongImgLeft = LONG_IMG_Left; 316 | } 317 | } 318 | } 319 | 320 | this.imgTop = this.LongImgTop; 321 | this.imgLeft = this.LongImgLeft; 322 | 323 | if (this.canUseCanvas) { 324 | this.drawImg(); 325 | } else { 326 | this.setImgStyle(); 327 | } 328 | } 329 | 330 | /** 331 | * 鼠标拖动的事件 332 | * @param moveFlag : 是否能移动的标志位 333 | * @param e 334 | */ 335 | public MoveCanvas(moveFlag: boolean, e: any) { 336 | if (moveFlag) { 337 | const cWidth = this.cWidth; 338 | const cHeight = this.cHeight; 339 | 340 | if (this.cImgHeight < cHeight && this.cImgWidth < cWidth) { 341 | return; 342 | } 343 | 344 | const { clientX, clientY } = e; 345 | 346 | const curX = this.curPos.x; 347 | const curY = this.curPos.y; 348 | 349 | if (this.cImgHeight > this.cHeight) { 350 | this.LongImgTop = this.LongImgTop + (clientY - this.curPos.y); 351 | 352 | if (clientY - curY > 0) { 353 | if (this.LongImgTop >= LONG_IMG_TOP) { 354 | this.LongImgTop = LONG_IMG_TOP; 355 | } 356 | } else { 357 | if ((-this.LongImgTop) > this.cImgHeight + LONG_IMG_TOP - window.innerHeight + this.footerHeight) { 358 | this.LongImgTop = -(this.cImgHeight + LONG_IMG_TOP - window.innerHeight + this.footerHeight); 359 | } 360 | } 361 | } 362 | 363 | if (this.cImgWidth > this.cWidth) { 364 | this.LongImgLeft = this.LongImgLeft + (clientX - this.curPos.x); 365 | 366 | if (clientX - curX > 0) { 367 | if (this.LongImgLeft >= LONG_IMG_Left) { 368 | this.LongImgLeft = LONG_IMG_Left; 369 | } 370 | } else { 371 | if ((-this.LongImgLeft) > this.cImgWidth + LONG_IMG_Left - window.innerWidth + this.sidebarWidth) { 372 | this.LongImgLeft = -(this.cImgWidth + LONG_IMG_Left - window.innerWidth + this.sidebarWidth); 373 | } 374 | } 375 | 376 | this.imgLeft = this.LongImgLeft; 377 | } 378 | 379 | this.curPos.x = clientX; 380 | this.curPos.y = clientY; 381 | 382 | this.imgTop = this.LongImgTop; 383 | this.imgLeft = this.LongImgLeft; 384 | 385 | 386 | if (this.canUseCanvas) { 387 | this.drawImg(); 388 | } else { 389 | this.setImgStyle(); 390 | } 391 | } 392 | } 393 | 394 | /** 395 | * 加载图片 396 | * @param callback :图片加载完成后要做的事情 397 | */ 398 | private loadimg(callback: any) { 399 | const imgDom = new Image(); 400 | imgDom.src = this.imgUrl; 401 | // imgDom.setAttribute("crossOrigin",'anonymous'); 402 | 403 | const _this = this; 404 | 405 | imgDom.onload = function() { 406 | _this.image = this; 407 | _this.setEachSizeArr(); 408 | callback(); 409 | } 410 | } 411 | 412 | /** 413 | * 计算图片放大、缩小各尺寸的大小数组, 414 | */ 415 | private setEachSizeArr () { 416 | const image = this.image; 417 | 418 | const EachSizeWidthArray: number[] = getEachSizeWidthArray({ 419 | naturalWidth: image.width, 420 | naturalHeight: image.height, 421 | }) 422 | 423 | this.EachSizeWidthArray = EachSizeWidthArray; 424 | 425 | const fixScreenSize = getFixScreenIndex({ 426 | naturalWidth: image.width, 427 | naturalHeight: image.height, 428 | wrapperWidth: this.cWidth, 429 | wrapperHeight: this.cHeight, 430 | }, EachSizeWidthArray); 431 | 432 | this.fixScreenSize = fixScreenSize; 433 | } 434 | } 435 | 436 | export default ImgToCanvas; -------------------------------------------------------------------------------- /src/components/photoGallery/components/Canvas.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useCallback } from 'react'; 2 | import ImgToCanvas from '../canvas'; 3 | import classNames from 'classnames'; 4 | import styles from './canvas.less'; 5 | 6 | interface winSize { 7 | width: number; 8 | height: number; 9 | } 10 | 11 | interface Props { 12 | WinSize: winSize; 13 | canUseCanvas: boolean; 14 | 15 | curSize: number; // 现在是放大的第几个 16 | imgUrl: string; 17 | imgLoading: boolean; // 图片加载 18 | 19 | setCurSize(cueSize: number): void; 20 | setImgLoading(bool: boolean): void; 21 | setFixScreenSize(fixScreenSize: number): void; 22 | 23 | WheelUpdate(e: any, instance: any): void; 24 | MouseDown(e: any, instance: any): void; 25 | MouseMove(e: any, instance: any): void; 26 | MouseUp(e: any): void; 27 | Click(): void; 28 | 29 | hideModal?(): void; 30 | } 31 | 32 | const Canvas = (props: Props): JSX.Element => { 33 | const { 34 | imgUrl, 35 | WinSize: size, 36 | canUseCanvas, 37 | curSize, 38 | imgLoading, 39 | } = props; 40 | 41 | const winWidth = size.width; 42 | const winHeight = size.height; 43 | 44 | let canvasRef: any = useRef(); 45 | let canvasInstance: any = useRef(null); 46 | 47 | let prevSizeRef: any = useRef(props.curSize); 48 | 49 | useEffect((): any=>{ 50 | window.addEventListener('mouseup', MouseUp, false); 51 | 52 | return ()=>{ 53 | window.removeEventListener('mouseup', MouseUp, false); 54 | } 55 | }, [canvasRef]); 56 | 57 | useEffect((): void => { 58 | if (canvasInstance.current) { 59 | const canvasClass = canvasInstance.current; 60 | 61 | canvasClass.winWidth = winWidth; 62 | canvasClass.winHeight = winHeight; 63 | 64 | canvasClass.canvasInit(); 65 | } 66 | }, [winWidth, winHeight]); 67 | 68 | useEffect((): void => { 69 | if (!canvasInstance.current) return; 70 | 71 | const canvasClass = canvasInstance.current; 72 | 73 | if (canvasClass.image) { 74 | canvasClass.changeCurSizeIndex(curSize); 75 | prevSizeRef.current = curSize; 76 | } 77 | }, [curSize]); 78 | 79 | useEffect((): void => { 80 | if (canvasInstance.current) canvasInstance.current = null; 81 | const canvasNode = canvasRef.current; 82 | canvasInstance.current = new ImgToCanvas(canvasNode, { 83 | imgUrl, 84 | winWidth, 85 | winHeight, 86 | canUseCanvas, 87 | loadingComplete: function(instance) { 88 | props.setImgLoading(false); 89 | props.setCurSize(instance.curScaleIndex); 90 | props.setFixScreenSize(instance.fixScreenSize); 91 | }, 92 | }); 93 | }, [imgUrl]); 94 | 95 | const WheelHandler = useCallback((e: any) => { 96 | 97 | if (!canvasInstance.current) return; 98 | e.stopPropagation(); 99 | 100 | const instance = canvasInstance.current; 101 | 102 | const { WheelUpdate } = props; 103 | 104 | WheelUpdate(e, instance); 105 | }, []) 106 | 107 | const MouseDown = useCallback((e: any) => { 108 | if (!canvasInstance.current) return; 109 | e.stopPropagation(); 110 | 111 | const instance = canvasInstance.current; 112 | 113 | const { MouseDown } = props; 114 | 115 | MouseDown(e, instance); 116 | }, []) 117 | 118 | const MouseMove = useCallback((e: any) => { 119 | if (!canvasInstance.current) return; 120 | e.stopPropagation(); 121 | 122 | const instance = canvasInstance.current; 123 | 124 | const { MouseMove } = props; 125 | 126 | MouseMove(e, instance); 127 | }, []) 128 | 129 | const MouseUp = useCallback((e: any) => { 130 | e.stopPropagation(); 131 | 132 | const { MouseUp } = props; 133 | 134 | MouseUp(e); 135 | }, []) 136 | 137 | const CanvasClick = useCallback(() => { 138 | const { Click } = props; 139 | Click(); 140 | }, []); 141 | 142 | /** 143 | * canvas 需要在渲染过程中就需要去加载,在 useEffect 中会出现闪顿 144 | */ 145 | if (canvasInstance.current) { 146 | if (prevSizeRef.current === curSize) { 147 | const canvasClass = canvasInstance.current; 148 | canvasClass.canvasInit(); 149 | } 150 | } 151 | 152 | return ( 153 | 170 | Your browser does not support the HTML5 canvass tag. 171 | 172 | ); 173 | }; 174 | 175 | export default Canvas; -------------------------------------------------------------------------------- /src/components/photoGallery/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './footer.less'; 3 | 4 | interface Props { 5 | curSize: number; // 现在是放大的第几个 6 | fixScreenSize: number; // 适应屏幕的 7 | imgsLens: number; 8 | currentImgIndex: number; 9 | setCurSize(cueSize: number): void; 10 | } 11 | 12 | const Footer = (props: Props): JSX.Element => { 13 | 14 | const { imgsLens, currentImgIndex } = props; 15 | 16 | return ( 17 |
18 | 19 | 20 | {currentImgIndex}/{imgsLens} 21 | 22 | 23 |
24 | { 28 | const curSize = props.curSize; 29 | props.setCurSize(curSize + 1) 30 | } 31 | } 32 | > 33 | 34 | 35 | { 39 | const curSize = props.curSize; 40 | props.setCurSize(curSize - 1) 41 | } 42 | } 43 | > 44 | {/* 缩小 */} 45 | 46 | 47 | { 51 | props.setCurSize(6); // 6 代表原尺寸 52 | } 53 | } 54 | > 55 | 56 | 57 | { 61 | const fixScreenSize = props.fixScreenSize; 62 | props.setCurSize(fixScreenSize); // -1 适应屏幕 63 | } 64 | } 65 | > 66 | 67 | 68 | { 72 | console.log('--下载了---'); 73 | } 74 | } 75 | > 76 | 77 | 78 |
79 | 80 |
81 | ); 82 | }; 83 | 84 | export default Footer; -------------------------------------------------------------------------------- /src/components/photoGallery/components/Image.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect } from 'react'; 2 | import ImgToCanvas from '../canvas'; 3 | import classNames from 'classnames'; 4 | import styles from './image.less'; 5 | 6 | interface winSize { 7 | width: number; 8 | height: number; 9 | } 10 | 11 | interface Props { 12 | WinSize: winSize; 13 | imgUrl: string; 14 | canUseCanvas: boolean; 15 | WheelUpdate(e: any, instance: any): void; 16 | MouseDown(e: any, instance: any): void; 17 | MouseMove(e: any, instance: any): void; 18 | MouseUp(e: any): void; 19 | Click(): void; 20 | } 21 | 22 | const Image = (props: Props): JSX.Element => { 23 | const { 24 | imgUrl, 25 | WinSize: size, 26 | canUseCanvas, 27 | } = props; 28 | 29 | const winWidth = size.width; 30 | const winHeight = size.height; 31 | 32 | let imageRef: any = useRef(); 33 | let imageInstance: any = useRef(null); 34 | 35 | useEffect((): any=>{ 36 | window.addEventListener('mouseup', MouseUp, false); 37 | 38 | return ()=>{ 39 | window.removeEventListener('mouseup', MouseUp, false); 40 | } 41 | }, [imageRef]); 42 | 43 | useEffect((): void => { 44 | const imageNode = imageRef.current; 45 | 46 | if (imageInstance.current) { 47 | const canvasClass = imageInstance.current; 48 | 49 | canvasClass.winWidth = winWidth; 50 | canvasClass.winHeight = winHeight; 51 | 52 | canvasClass.imageInit(); 53 | } else { 54 | imageInstance.current = new ImgToCanvas(imageNode, { 55 | imgUrl, 56 | winWidth, 57 | winHeight, 58 | canUseCanvas: false, 59 | loadingComplete: function() { 60 | }, 61 | }); 62 | } 63 | 64 | }, [imageRef, winWidth, winHeight]); 65 | 66 | 67 | const CanvasClick = () => { 68 | const { Click } = props; 69 | Click(); 70 | } 71 | 72 | const WheelHandler = (e: any) => { 73 | if (!imageInstance.current) return; 74 | e.stopPropagation(); 75 | 76 | const instance = imageInstance.current; 77 | 78 | const { WheelUpdate } = props; 79 | 80 | WheelUpdate(e, instance); 81 | } 82 | 83 | const MouseDown = (e: any) => { 84 | if (!imageInstance.current) return; 85 | e.stopPropagation(); 86 | 87 | const instance = imageInstance.current; 88 | 89 | const { MouseDown } = props; 90 | 91 | MouseDown(e, instance); 92 | } 93 | 94 | const MouseMove = (e: any) => { 95 | if (!imageInstance.current) return; 96 | e.stopPropagation(); 97 | 98 | const instance = imageInstance.current; 99 | 100 | const { MouseMove } = props; 101 | 102 | MouseMove(e, instance); 103 | } 104 | 105 | const MouseUp = (e: any) => { 106 | e.stopPropagation(); 107 | const { MouseUp } = props; 108 | 109 | MouseUp(e); 110 | } 111 | 112 | const handleDragStart = (e: any): void => { 113 | e.preventDefault(); 114 | } 115 | 116 | const handleDragEnd = (e: any): void => { 117 | e.preventDefault(); 118 | } 119 | 120 | return ( 121 | 140 | ); 141 | }; 142 | 143 | export default Image; -------------------------------------------------------------------------------- /src/components/photoGallery/components/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './sidebar.less'; 3 | 4 | interface recordImg { 5 | url: string; 6 | size?: string; 7 | width?: string; 8 | height?: string; 9 | is_onlocal?: string; 10 | } 11 | 12 | interface Props { 13 | imgData: recordImg[]; 14 | curImgUrl?: string; 15 | setCurrentImgIndex(index: number): void; 16 | setImgLoading(bool: boolean): void; 17 | setImgUrl(imgUrl: string): void; 18 | } 19 | 20 | const Sidebar = (props: Props): JSX.Element => { 21 | const { curImgUrl, imgData } = props; 22 | 23 | return ( 24 |
25 | { 26 | imgData.map((_, index) => { 27 | return ( 28 |
32 | { 36 | if (curImgUrl === _.url) return; 37 | props.setImgUrl(_.url); 38 | props.setCurrentImgIndex(index + 1); 39 | props.setImgLoading(true); 40 | } 41 | } 42 | > 43 | 44 | 45 |
46 | ); 47 | }) 48 | } 49 |
50 | ); 51 | }; 52 | 53 | export default Sidebar; -------------------------------------------------------------------------------- /src/components/photoGallery/components/canvas.less: -------------------------------------------------------------------------------- 1 | .isImgLoading { 2 | display: none; 3 | } -------------------------------------------------------------------------------- /src/components/photoGallery/components/canvas.less.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace CanvasLessModule { 2 | export interface ICanvasLess { 3 | isImgLoading: string; 4 | } 5 | } 6 | 7 | declare const CanvasLessModule: CanvasLessModule.ICanvasLess & { 8 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 9 | locals: CanvasLessModule.ICanvasLess; 10 | }; 11 | 12 | export = CanvasLessModule; 13 | -------------------------------------------------------------------------------- /src/components/photoGallery/components/footer.less: -------------------------------------------------------------------------------- 1 | @import "../fonts/iconfont.less"; 2 | 3 | .footer { 4 | position: absolute; 5 | right: 0; 6 | bottom: 0; 7 | left: 0; 8 | width: calc(100% - 120px); 9 | height: 50px; 10 | margin-left: 0; 11 | text-align: left; 12 | line-height: 50px; 13 | background-color: rgba(34,34,34,.88); 14 | 15 | .toolbarIndex { 16 | font-size: 16px; 17 | color: #fff; 18 | 19 | .toolbarIndexNumber { 20 | margin-left: 40px; 21 | } 22 | } 23 | 24 | .toolBarActions { 25 | position: absolute; 26 | right: 0; 27 | bottom: 0; 28 | height: 50px; 29 | 30 | .toolBarItems { 31 | position: relative; 32 | display: inline-block; 33 | width: 40px; 34 | height: 50px; 35 | text-align: center; 36 | font-size: 24px; 37 | line-height: 24px; 38 | padding: 0 13px; 39 | text-decoration: none; 40 | color: #fff; 41 | opacity: .7; 42 | 43 | .icon { 44 | display: block; 45 | font-size: 24px; 46 | width: 24px; 47 | height: 22px; 48 | margin-left: 8px; 49 | margin-top: 14px; 50 | pointer-events: none; 51 | } 52 | 53 | &:hover { 54 | background-color: #000; 55 | opacity: 1; 56 | } 57 | } 58 | } 59 | 60 | // span { 61 | // color: #fff; 62 | // font-size: 12px; 63 | // padding: 6px 16px; 64 | // border-radius: 4px; 65 | // border: 1px solid #fff; 66 | // cursor: pointer; 67 | // margin-left: 24px; 68 | 69 | // &:hover { 70 | // background: rgba(255,255,255, .3); 71 | // } 72 | 73 | // &:first-child { 74 | // margin-left: 100px; 75 | // } 76 | // } 77 | } -------------------------------------------------------------------------------- /src/components/photoGallery/components/footer.less.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace FooterLessModule { 2 | export interface IFooterLess { 3 | footer: string; 4 | icon: string; 5 | "icon-552cc1b6033d0": string; 6 | "icon-Plus": string; 7 | "icon-down": string; 8 | "icon-ellipsis2": string; 9 | "icon-fangda": string; 10 | "icon-jian": string; 11 | "icon-left": string; 12 | "icon-right": string; 13 | "icon-shuangyoujiantou-": string; 14 | "icon-shuangzuojiantou-": string; 15 | "icon-suoxiao": string; 16 | "icon-top": string; 17 | "icon-xiazai": string; 18 | icon552Cc1B6033D0: string; 19 | iconDown: string; 20 | iconEllipsis2: string; 21 | iconFangda: string; 22 | iconJian: string; 23 | iconLeft: string; 24 | iconPlus: string; 25 | iconRight: string; 26 | iconShuangyoujiantou: string; 27 | iconShuangzuojiantou: string; 28 | iconSuoxiao: string; 29 | iconTop: string; 30 | iconXiazai: string; 31 | iconfont: string; 32 | toolBarActions: string; 33 | toolBarItems: string; 34 | toolbarIndex: string; 35 | toolbarIndexNumber: string; 36 | } 37 | } 38 | 39 | declare const FooterLessModule: FooterLessModule.IFooterLess & { 40 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 41 | locals: FooterLessModule.IFooterLess; 42 | }; 43 | 44 | export = FooterLessModule; 45 | -------------------------------------------------------------------------------- /src/components/photoGallery/components/image.less: -------------------------------------------------------------------------------- 1 | .imgBox { 2 | position: absolute; 3 | right: 0; 4 | bottom: 0; 5 | border: 0; 6 | background: #fff; 7 | box-shadow: 0 0 28px rgba(0,0,0,.2); 8 | margin: 0px; 9 | max-width: none; 10 | max-height: none; 11 | } 12 | 13 | .isVisiblity { 14 | opacity: 0; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/photoGallery/components/image.less.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace ImageLessModule { 2 | export interface IImageLess { 3 | imgBox: string; 4 | isVisiblity: string; 5 | } 6 | } 7 | 8 | declare const ImageLessModule: ImageLessModule.IImageLess & { 9 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 10 | locals: ImageLessModule.IImageLess; 11 | }; 12 | 13 | export = ImageLessModule; 14 | -------------------------------------------------------------------------------- /src/components/photoGallery/components/index.ts: -------------------------------------------------------------------------------- 1 | import Footer from './Footer'; 2 | import Sidebar from './Sidebar'; 3 | import Canvas from './Canvas'; 4 | import Image from './Image'; 5 | 6 | export { 7 | Footer, 8 | Sidebar, 9 | Canvas, 10 | Image, 11 | }; 12 | -------------------------------------------------------------------------------- /src/components/photoGallery/components/sidebar.less: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | position: absolute; 3 | top: 0; 4 | right: 0; 5 | bottom: 0; 6 | left: auto; 7 | width: 120px; 8 | height: auto; 9 | overflow-y: auto; 10 | overflow-x: hidden; 11 | background-color: #0a0a0a; 12 | box-shadow: 0 0 18px rgba(0,0,0,.6); 13 | 14 | .imgItem { 15 | margin: 10px; 16 | width: 90px; 17 | height: 60px; 18 | overflow: hidden; 19 | border: 1px solid hsla(0,0%,100%,.3); 20 | cursor: pointer; 21 | 22 | .imgSpan { 23 | opacity: .6; 24 | width: 100%; 25 | height: 100%; 26 | display: block; 27 | margin: auto; 28 | border: 0; 29 | 30 | img { 31 | width: 100%; 32 | height: 100%; 33 | vertical-align: middle; 34 | object-fit: contain; 35 | } 36 | } 37 | 38 | &:hover { 39 | .imgSpan { 40 | opacity: 1; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/photoGallery/components/sidebar.less.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace SidebarLessModule { 2 | export interface ISidebarLess { 3 | imgItem: string; 4 | imgSpan: string; 5 | sidebar: string; 6 | } 7 | } 8 | 9 | declare const SidebarLessModule: SidebarLessModule.ISidebarLess & { 10 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 11 | locals: SidebarLessModule.ISidebarLess; 12 | }; 13 | 14 | export = SidebarLessModule; 15 | -------------------------------------------------------------------------------- /src/components/photoGallery/fonts/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrell0904/darrell-photo-gallery/f008fb722b9414966ac9c9c357f34e9561145291/src/components/photoGallery/fonts/iconfont.eot -------------------------------------------------------------------------------- /src/components/photoGallery/fonts/iconfont.less: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1580202769445'); /* IE9 */ 3 | src: url('iconfont.eot?t=1580202769445#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), 5 | url('iconfont.woff?t=1580202769445') format('woff'), 6 | url('iconfont.ttf?t=1580202769445') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1580202769445#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-552cc1b6033d0:before { 19 | content: "\e601"; 20 | } 21 | 22 | .icon-suoxiao:before { 23 | content: "\e616"; 24 | } 25 | 26 | .icon-left:before { 27 | content: "\e609"; 28 | } 29 | 30 | .icon-right:before { 31 | content: "\e61b"; 32 | } 33 | 34 | .icon-top:before { 35 | content: "\e626"; 36 | } 37 | 38 | .icon-Plus:before { 39 | content: "\e60b"; 40 | } 41 | 42 | .icon-jian:before { 43 | content: "\e620"; 44 | } 45 | 46 | .icon-fangda:before { 47 | content: "\e826"; 48 | } 49 | 50 | .icon-down:before { 51 | content: "\e61a"; 52 | } 53 | 54 | .icon-shuangyoujiantou-:before { 55 | content: "\e62a"; 56 | } 57 | 58 | .icon-shuangzuojiantou-:before { 59 | content: "\e62b"; 60 | } 61 | 62 | .icon-xiazai:before { 63 | content: "\e7de"; 64 | } 65 | 66 | .icon-ellipsis2:before { 67 | content: "\e701"; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/components/photoGallery/fonts/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/components/photoGallery/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrell0904/darrell-photo-gallery/f008fb722b9414966ac9c9c357f34e9561145291/src/components/photoGallery/fonts/iconfont.ttf -------------------------------------------------------------------------------- /src/components/photoGallery/fonts/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrell0904/darrell-photo-gallery/f008fb722b9414966ac9c9c357f34e9561145291/src/components/photoGallery/fonts/iconfont.woff -------------------------------------------------------------------------------- /src/components/photoGallery/fonts/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrell0904/darrell-photo-gallery/f008fb722b9414966ac9c9c357f34e9561145291/src/components/photoGallery/fonts/iconfont.woff2 -------------------------------------------------------------------------------- /src/components/photoGallery/index.less: -------------------------------------------------------------------------------- 1 | .modalWrapper { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | z-index: 10000; 6 | width: 100%; 7 | height: 100%; 8 | display: block; 9 | transition: background-color .2s ease-out; 10 | background-color: rgba(0,0,0,.65); 11 | display: none; 12 | 13 | &.showImgGallery { 14 | display: block; 15 | } 16 | 17 | .contentWrapper { 18 | height: 100%; 19 | 20 | .imgwrapper { 21 | position: absolute; 22 | width: calc(100% - 120px); 23 | height: 100%; 24 | top: 0; 25 | right: 0; 26 | bottom: 0; 27 | left: 0; 28 | right: auto; 29 | margin: auto; 30 | cursor: pointer; 31 | transition: all .2s ease-out; 32 | 33 | .loadingImg { 34 | position: absolute; 35 | top: calc(50% - 50px); 36 | left: 50%; 37 | display: none; 38 | 39 | &.imgLoding { 40 | display: inline-block; 41 | } 42 | } 43 | 44 | canvas { 45 | width: 100%; 46 | height: calc(100% - 50px); 47 | } 48 | } 49 | 50 | .sidebar { 51 | position: absolute; 52 | top: 0; 53 | right: 0; 54 | bottom: 0; 55 | left: auto; 56 | width: 120px; 57 | height: auto; 58 | overflow-y: auto; 59 | overflow-x: hidden; 60 | background-color: #0a0a0a; 61 | box-shadow: 0 0 18px rgba(0,0,0,.6); 62 | } 63 | 64 | .footer { 65 | position: absolute; 66 | right: 0; 67 | bottom: 0; 68 | left: 0; 69 | width: auto; 70 | height: 50px; 71 | margin-left: 0; 72 | text-align: left; 73 | line-height: 50px; 74 | background-color: rgba(34,34,34,.88); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/components/photoGallery/index.less.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IndexLessModule { 2 | export interface IIndexLess { 3 | contentWrapper: string; 4 | footer: string; 5 | imgLoding: string; 6 | imgwrapper: string; 7 | loadingImg: string; 8 | modalWrapper: string; 9 | showImgGallery: string; 10 | sidebar: string; 11 | } 12 | } 13 | 14 | declare const IndexLessModule: IndexLessModule.IIndexLess & { 15 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ 16 | locals: IndexLessModule.IIndexLess; 17 | }; 18 | 19 | export = IndexLessModule; 20 | -------------------------------------------------------------------------------- /src/components/photoGallery/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback } from 'react'; 2 | import { canUseCanvas } from '@utils/utils'; 3 | import * as loadingImg from '@images/loading.gif'; 4 | import classNames from 'classnames'; 5 | import { Footer, Sidebar, Canvas, Image } from './components'; 6 | import "@components/photoGallery/fonts/iconfont.less"; 7 | import * as styles from './index.less'; 8 | 9 | interface recordImg { 10 | url: string; 11 | size?: string; 12 | width?: string; 13 | height?: string; 14 | is_onlocal?: string; 15 | [propName: string]: any; 16 | } 17 | 18 | interface Props { 19 | visible: boolean; 20 | currentImg: number; 21 | imgData: recordImg[]; 22 | hideModal?(): void; 23 | } 24 | 25 | interface curPos { 26 | x: number, 27 | y: number, 28 | } 29 | 30 | let moveFlag: boolean = false; 31 | 32 | let StartPos: curPos = { 33 | x: 0, 34 | y: 0, 35 | } 36 | 37 | let DoClick: boolean = true; 38 | 39 | const NATURAL_CUR_INDEX = 6; // 图片原图 在放大缩小数组中的 索引 40 | 41 | function useWinSize(){ 42 | const [ size , setSize] = useState({ 43 | width: document.documentElement.clientWidth, 44 | height: document.documentElement.clientHeight 45 | }); 46 | 47 | const onResize = useCallback(()=>{ 48 | setSize({ 49 | width: document.documentElement.clientWidth, 50 | height: document.documentElement.clientHeight, 51 | }) 52 | }, []); 53 | 54 | useEffect(()=>{ 55 | window.addEventListener('resize', onResize, false); 56 | 57 | return ()=>{ 58 | window.removeEventListener('resize', onResize, false); 59 | } 60 | }, []) 61 | 62 | return size; 63 | } 64 | 65 | const photoGallery = (props: Props): JSX.Element => { 66 | const { imgData, currentImg, visible } = props; 67 | 68 | const [imgUrl, setImgUrl] = useState(imgData[currentImg - 1].url); 69 | const [curSize, setCurSize] = useState(NATURAL_CUR_INDEX); 70 | const [fixScreenSize, setFixScreenSize] = useState(NATURAL_CUR_INDEX); 71 | 72 | const [currentImgIndex, setCurrentImgIndex] = useState(currentImg); 73 | 74 | const [imgLoading, setImgLoading] = useState(true); 75 | 76 | const winSize = useWinSize(); 77 | 78 | const WheelUpdate = (e: any, instance: any) => { 79 | // 执行滚轮事件 80 | instance.WheelUpdate(e); 81 | } 82 | 83 | const MouseDown = useCallback((e: any, instance: any) => { 84 | moveFlag = true; 85 | const { clientX, clientY } = e; 86 | 87 | instance.curPos.x = clientX; 88 | instance.curPos.y = clientY; 89 | 90 | StartPos.x = clientX; 91 | StartPos.y = clientY; 92 | }, []); 93 | 94 | const MouseMove = useCallback((e: any, instance: any) => { 95 | instance.MoveCanvas(moveFlag, e); 96 | }, []) 97 | 98 | const MouseUp = useCallback((e: any) => { 99 | moveFlag = false; 100 | 101 | if (e.clientX === StartPos.x && e.clientY === StartPos.y) { 102 | DoClick = true; 103 | } else { 104 | DoClick = false; 105 | } 106 | }, []) 107 | 108 | const Click = useCallback(() => { 109 | if (!DoClick) return; 110 | const { hideModal } = props; 111 | if (hideModal) { 112 | hideModal(); 113 | } 114 | }, []); 115 | 116 | // canvas 117 | // 设置 适应屏幕尺寸 118 | const _setFixScreenSize = useCallback((curSize) => { 119 | if (curSize < 0) { 120 | curSize = 0 121 | } 122 | 123 | if (curSize > 12) { 124 | curSize = 12; 125 | } 126 | setFixScreenSize(curSize); 127 | }, []); 128 | 129 | // 设置当前图片尺寸 130 | const _setCurSize = useCallback((curSize) => { 131 | if (curSize < 0) { 132 | curSize = 0 133 | } 134 | 135 | if (curSize > 12) { 136 | curSize = 12; 137 | } 138 | setCurSize(curSize); 139 | }, []); 140 | 141 | // 图片 loading 142 | const _setImgLoading = useCallback((bool) => { 143 | setImgLoading(bool); 144 | }, []); 145 | 146 | // sidebar 147 | // 设置图片 url 148 | const _setImgUrl = useCallback((imgUrl) => { 149 | setImgUrl(imgUrl); 150 | }, []) 151 | 152 | // 设置当前页面索引 153 | const _setCurrentImgIndex = useCallback((index) => { 154 | setCurrentImgIndex(index); 155 | }, []) 156 | 157 | 158 | 159 | let wrapperDom = null; 160 | 161 | if (canUseCanvas) { 162 | wrapperDom = ( 163 | 178 | ) 179 | } else { 180 | wrapperDom = ( 181 | 191 | ) 192 | } 193 | 194 | return ( 195 |
205 |
206 |
207 | loading 219 | { wrapperDom } 220 |
221 | 228 |
235 |
236 |
237 | ); 238 | } 239 | 240 | export default photoGallery; 241 | -------------------------------------------------------------------------------- /src/images/images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg'; 2 | declare module '*.png'; 3 | declare module '*.jpg'; 4 | declare module '*.jpeg'; 5 | declare module '*.gif'; 6 | declare module '*.bmp'; 7 | declare module '*.tiff'; -------------------------------------------------------------------------------- /src/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrell0904/darrell-photo-gallery/f008fb722b9414966ac9c9c357f34e9561145291/src/images/loading.gif -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /src/index.less: -------------------------------------------------------------------------------- 1 | .wrapper{ 2 | color: red; 3 | } -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import PhotoGallery from './components/photoGallery/index'; // 使用大写 2 | 3 | export default PhotoGallery; -------------------------------------------------------------------------------- /src/types/global/window.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | mozRequestAnimationFrame: any; 4 | oRequestAnimationFrame: any; 5 | mozCancelAnimationFrame: any; 6 | oCancelAnimationFrame: any; 7 | } 8 | } 9 | 10 | export = global; -------------------------------------------------------------------------------- /src/utils/img.ts: -------------------------------------------------------------------------------- 1 | import { findCloseSet } from '@utils/utils'; 2 | 3 | /** 4 | * 获取图片在 显示区域中 的 宽高,top,left 5 | */ 6 | interface RectWidth { 7 | naturalWidth: number; 8 | naturalHeight: number; 9 | wrapperWidth: number; 10 | wrapperHeight: number; 11 | curImgWidth?: number; 12 | curImgHeight?: number; 13 | curImgTop?: number; 14 | curImgLeft?: number; 15 | winWidth?: number; 16 | } 17 | 18 | interface BoundingClientRect { 19 | imgTop: number; 20 | imgLeft: number; 21 | ImgHeight: number; 22 | ImgWidth: number; 23 | } 24 | 25 | interface NaturalImg { 26 | naturalWidth: number; 27 | naturalHeight: number; 28 | } 29 | 30 | // const LONG_IMG_TOP: number = 10; // 长图距离顶部和底部的距离 31 | // const LONG_IMG_Left: number = 10; // 长图距离顶部和底部的距离 32 | 33 | 34 | const SCALE_TO_NATURAL : number = 4; // 放大到原来的4倍 35 | const MINI_TO_NATURAL: number = 10; // 缩小到原来的10倍 36 | 37 | const SCALE_TIMES: number = 6; // 缩小到原来的10倍 38 | 39 | const CUR_SCALE_INDEX: number = 6; // 现在的尺寸在哪一个地方 40 | // const FIX_SCREEN_SIZE: number = 6; // 适应屏幕的 size 41 | 42 | export const getBoundingClientRect = (options: RectWidth): BoundingClientRect => { 43 | const { 44 | naturalWidth, 45 | naturalHeight, 46 | wrapperWidth, 47 | wrapperHeight, 48 | curImgWidth, 49 | curImgHeight, 50 | curImgTop, 51 | curImgLeft, 52 | winWidth, 53 | } = options; 54 | 55 | const imageRadio = naturalWidth / naturalHeight; 56 | const imageHightRadio = naturalHeight / naturalWidth; 57 | 58 | const wrapperRadio = wrapperWidth / wrapperHeight; 59 | const wrapperHeightRadio = wrapperHeight / wrapperWidth; 60 | 61 | 62 | const newWinWidth = (winWidth! - 120) / 2; 63 | 64 | let imgTop : number = curImgTop!; 65 | let imgLeft: number = curImgLeft!; 66 | let ImgHeight: number = curImgHeight!; 67 | let ImgWidth: number = curImgWidth!; 68 | 69 | if (imageRadio <= 1) { 70 | imgTop = wrapperHeight * 0.05; 71 | 72 | ImgHeight = wrapperHeight - wrapperHeight * 0.05 * 2; 73 | ImgWidth = ImgHeight * naturalWidth / naturalHeight; 74 | 75 | if (wrapperRadio <= imageRadio) { 76 | ImgWidth = wrapperWidth - wrapperWidth * 0.05 * 2; 77 | ImgHeight = ImgWidth * naturalHeight / naturalWidth; 78 | 79 | imgTop = (wrapperHeight - ImgHeight) / 2 80 | } 81 | 82 | // 如果图片实际宽度小于 cabnvas 的宽度 83 | // 图片的宽度就是原图,高度就是等比例 84 | // 图片宽高比 小于 55 %,需要展示全部高度图片,即图片可以滚动 85 | 86 | if (wrapperWidth >= naturalWidth && imageRadio <= 0.55) { 87 | ImgWidth = naturalWidth; 88 | ImgHeight = ImgWidth * naturalHeight / naturalWidth; 89 | imgTop = (wrapperHeight - ImgHeight) / 2; 90 | } 91 | 92 | imgLeft = newWinWidth - ImgWidth / 2; 93 | } 94 | 95 | if (imageHightRadio < 1) { 96 | ImgWidth = wrapperWidth - wrapperWidth * 0.05 * 2; 97 | ImgHeight = ImgWidth * naturalHeight / naturalWidth; 98 | 99 | imgTop = (wrapperHeight - ImgHeight) / 2; 100 | 101 | if (wrapperHeightRadio <= imageHightRadio) { 102 | ImgHeight = wrapperHeight - wrapperHeight * 0.05 * 2; 103 | ImgWidth = ImgHeight * naturalWidth / naturalHeight; 104 | 105 | imgTop = wrapperHeight * 0.05; 106 | } 107 | 108 | imgLeft = newWinWidth - ImgWidth / 2; 109 | } 110 | 111 | return { 112 | imgLeft, 113 | imgTop, 114 | ImgWidth, 115 | ImgHeight, 116 | } 117 | } 118 | 119 | /** 120 | * 获取各尺寸的图片大小,默认最大是原图的 4 倍,最小是原图的 1/10;总共能放大缩小六次 121 | */ 122 | export const getEachSizeWidthArray = (options: NaturalImg) => { 123 | const { naturalWidth } = options; 124 | 125 | const scaleToNatural = SCALE_TO_NATURAL; 126 | const miniToNatural = MINI_TO_NATURAL; 127 | 128 | const scaleTimes: number = SCALE_TIMES; 129 | 130 | const maxNaturalWidth: number = scaleToNatural * naturalWidth; 131 | const minNaturalWidth: number = naturalWidth / miniToNatural; 132 | 133 | const EachMaxSize: number = (maxNaturalWidth - naturalWidth) / scaleTimes; 134 | const EachMinSize: number = (naturalWidth - minNaturalWidth) / scaleTimes; 135 | 136 | let EachSizeWidthArray: number[] = []; 137 | 138 | EachSizeWidthArray = [ 139 | ...Array.from({length: scaleTimes}, (_v, i) => (minNaturalWidth + i * EachMinSize )), 140 | ...Array.from({length: scaleTimes + 1}, (_v, i) => (naturalWidth + i * EachMaxSize)) 141 | ] 142 | 143 | return EachSizeWidthArray; 144 | } 145 | 146 | /** 147 | * 获取适应屏幕 的 sizeIndex 148 | * @params EachSizeWidthArray:各尺寸的图片宽度 149 | */ 150 | export const getFixScreenIndex = (options: RectWidth, EachSizeWidthArray: number[]) => { 151 | const { naturalWidth, naturalHeight, wrapperWidth, wrapperHeight} = options; 152 | 153 | const right = EachSizeWidthArray.length - 1; 154 | 155 | let fixScreenSize: number = CUR_SCALE_INDEX; 156 | 157 | // 跳出整个循环? 158 | for (let i = right; i >= 0; i--) { 159 | const itemWidth = EachSizeWidthArray[i]; 160 | const itemHeight = itemWidth * naturalHeight / naturalWidth; 161 | 162 | if (itemWidth < wrapperWidth && itemHeight < wrapperHeight) { 163 | fixScreenSize = i; 164 | break; 165 | } 166 | } 167 | 168 | return fixScreenSize; 169 | } 170 | 171 | /** 172 | * 获取当前的 curIndex 173 | * @params EachSizeWidthArray:各尺寸的图片宽度 174 | * @params CurImgWidth:当前页面的大小 175 | */ 176 | export const getCurImgIndex = (EachSizeWidthArray: number[], CurImgWidth: number): number => { 177 | const cImgWidth = CurImgWidth; 178 | 179 | if (!EachSizeWidthArray) return CUR_SCALE_INDEX; 180 | 181 | const curScale = findCloseSet(EachSizeWidthArray, cImgWidth); 182 | 183 | const curScaleIndex = EachSizeWidthArray.indexOf(curScale); 184 | 185 | return curScaleIndex; 186 | } 187 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断能否使用 canvas 3 | */ 4 | export const canUseCanvas = !!document.createElement('canvas').getContext; 5 | 6 | /** 7 | * 二分查找 离数组最近的一个元素 8 | */ 9 | export const findCloseSet = (arr: number[], num: number) => { 10 | let left = 0; 11 | let right = arr.length - 1; 12 | 13 | while(left <= right){ 14 | 15 | let middle = Math.floor((right + left) / 2); 16 | 17 | if(right - left <= 1){ 18 | break; 19 | } 20 | 21 | let val = arr[middle]; 22 | 23 | if(val === num){ 24 | return val; 25 | } 26 | 27 | else if(val > num){ 28 | right = middle; 29 | } 30 | 31 | else{ 32 | left = middle; 33 | } 34 | } 35 | 36 | let leftValue = arr[left]; 37 | let rightValue = arr[right]; 38 | 39 | return rightValue - num > num - leftValue ? leftValue : rightValue; 40 | } 41 | 42 | const DEFAULT_INTERVAL = 1000 / 60; 43 | 44 | /** 45 | * requestAnimationFrame 46 | */ 47 | export const requestAnimationFrame = (() => ( 48 | window.requestAnimationFrame 49 | || window.webkitRequestAnimationFrame 50 | || window.mozRequestAnimationFrame 51 | || window.oRequestAnimationFrame 52 | 53 | // if all else fails, use setTimeout 54 | || function requestAnimationFrameTimeOut(callback: any) { 55 | // make interval as precise as possible. 56 | return window.setTimeout(callback, (callback.interval || DEFAULT_INTERVAL) / 2); 57 | } 58 | ) 59 | )(); 60 | 61 | /** 62 | * cancelAnimationFrame 63 | */ 64 | export const cancelAnimationFrame = (() => ( 65 | window.cancelAnimationFrame 66 | || window.webkitCancelAnimationFrame 67 | || window.mozCancelAnimationFrame 68 | || window.oCancelAnimationFrame 69 | // if all else fails, use setTimeout 70 | || function cancelAnimationFrameClearTimeOut(id) { 71 | window.clearTimeout(id); 72 | }) 73 | )(); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build", 4 | "module": "esnext", 5 | "target": "es5", 6 | "lib": ["es6", "dom", "es2016", "es2017"], 7 | "sourceMap": true, 8 | "allowJs": false, 9 | "jsx": "react", 10 | "declaration": true, 11 | "moduleResolution": "node", 12 | "forceConsistentCasingInFileNames": true, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | "noImplicitAny": true, 16 | "strictNullChecks": true, 17 | "suppressImplicitAnyIndexErrors": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "allowSyntheticDefaultImports": true, 21 | "baseUrl": ".", 22 | "paths": { 23 | "@utils/*": ["./src/utils/*"], 24 | "@images/*": ["src/images/*"], 25 | "@components/*": ["src/components/*"], 26 | }, 27 | }, 28 | "include": [ 29 | "./src/**/*", 30 | ], 31 | "exclude": ["node_modules", "build", "dist", "example"] 32 | } 33 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-implicit-dependencies": ["optional", ["src"]] 4 | } 5 | } --------------------------------------------------------------------------------