├── public ├── favicon.ico ├── images │ ├── grid.gif │ ├── next.png │ ├── prev.png │ ├── close.png │ ├── helix.gif │ ├── loading.gif │ ├── sphere.gif │ ├── table.gif │ └── qrcode_for_gh_15b711976a8a_258.jpg ├── index.php ├── .htaccess ├── wall2 │ ├── stats.min.js │ ├── CSS3DRenderer.js │ ├── tween.js │ └── OrbitControls.js ├── css │ ├── lightbox.min.css │ ├── wall.css │ └── wall2.css └── js │ ├── tween.min.js │ ├── lazyload.js │ ├── CSS3DRenderer.js │ ├── lightbox.min.js │ ├── wall.js │ ├── TrackballControls.js │ └── lightbox.js ├── screenshots ├── Cube.jpg ├── wall.jpg ├── Plane.jpg ├── Sphere.jpg └── Cylinder.jpg ├── logs └── README.md ├── src ├── middleware.php ├── controllers │ ├── Admin │ │ └── homeController.php │ ├── Users │ │ └── PhotosController.php │ └── Weixin │ │ └── IndexController.php ├── common │ ├── function │ │ └── file.php │ ├── Curl │ │ ├── Curlex.php │ │ └── error.ini │ ├── curl │ │ ├── Curlex.php │ │ └── error.ini │ ├── Image │ │ └── Photo.php │ └── image │ │ └── Photo.php ├── console │ ├── Base.php │ └── Qn.php ├── routes.php ├── models │ ├── Qiniu.php │ ├── User.php │ └── Message.php ├── dependencies.php └── settings.defailt.php ├── .gitignore ├── phpunit.xml ├── docker-compose.yml ├── cli.php ├── CONTRIBUTING.md ├── templates ├── index.phtml ├── subscribe.phtml ├── wall.phtml └── wall2.phtml ├── tests └── Functional │ ├── HomepageTest.php │ └── BaseTestCase.php ├── composer.json ├── README_CN.md ├── README.md └── db └── pper.sql /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /screenshots/Cube.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/screenshots/Cube.jpg -------------------------------------------------------------------------------- /screenshots/wall.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/screenshots/wall.jpg -------------------------------------------------------------------------------- /public/images/grid.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/public/images/grid.gif -------------------------------------------------------------------------------- /public/images/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/public/images/next.png -------------------------------------------------------------------------------- /public/images/prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/public/images/prev.png -------------------------------------------------------------------------------- /screenshots/Plane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/screenshots/Plane.jpg -------------------------------------------------------------------------------- /screenshots/Sphere.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/screenshots/Sphere.jpg -------------------------------------------------------------------------------- /logs/README.md: -------------------------------------------------------------------------------- 1 | Your Slim Framework application's log files will be written to this directory. 2 | -------------------------------------------------------------------------------- /public/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/public/images/close.png -------------------------------------------------------------------------------- /public/images/helix.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/public/images/helix.gif -------------------------------------------------------------------------------- /public/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/public/images/loading.gif -------------------------------------------------------------------------------- /public/images/sphere.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/public/images/sphere.gif -------------------------------------------------------------------------------- /public/images/table.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/public/images/table.gif -------------------------------------------------------------------------------- /screenshots/Cylinder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/screenshots/Cylinder.jpg -------------------------------------------------------------------------------- /src/middleware.php: -------------------------------------------------------------------------------- 1 | add(new \Slim\Csrf\Guard); 5 | -------------------------------------------------------------------------------- /public/images/qrcode_for_gh_15b711976a8a_258.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhill/pper/HEAD/public/images/qrcode_for_gh_15b711976a8a_258.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /composer.lock 2 | /vendor/ 3 | /logs/* 4 | !/logs/README.md 5 | /public/photo/* 6 | /public/resource 7 | /.idea/ 8 | /src/settings.php 9 | /db 10 | !/db/pper.sql 11 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/controllers/Admin/homeController.php: -------------------------------------------------------------------------------- 1 | ''] 16 | 17 | $qn = new Qn($configure['settings']); 18 | 19 | $application = new Application(); 20 | 21 | $application->add($qn); // 添加命令行 22 | 23 | $application->run(); -------------------------------------------------------------------------------- /src/common/function/file.php: -------------------------------------------------------------------------------- 1 | read()) 15 | { 16 | if($file !== "." && $file !== "..") 17 | { 18 | self::get_allfiles($path."/".$file, $files); 19 | } 20 | } 21 | $dp ->close(); 22 | } 23 | if(is_file($path)) 24 | { 25 | $files[] = $path; 26 | } 27 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ## Pull Requests 4 | 5 | 1. Fork the Slim Skeleton repository 6 | 2. Create a new branch for each feature or improvement 7 | 3. Send a pull request from each feature branch to the **3.x** branch 8 | 9 | It is very important to separate new features or improvements into separate feature branches, and to send a 10 | pull request for each branch. This allows us to review and pull in new features or improvements individually. 11 | 12 | ## Style Guide 13 | 14 | All pull requests must adhere to the [PSR-2 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). 15 | -------------------------------------------------------------------------------- /src/console/Base.php: -------------------------------------------------------------------------------- 1 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 27 | $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); 28 | $this->db = $pdo; 29 | 30 | $this->qiniu = $settings['qiniu']; 31 | parent::__construct(); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | run(); 33 | -------------------------------------------------------------------------------- /src/routes.php: -------------------------------------------------------------------------------- 1 | get('/home','\Controllers\Admin\HomeController:home'); 9 | $app->get('/users/[{id}]','\Controllers\Users\PhotosController:index'); 10 | $app->get('/wall/[{id}]','\Controllers\Users\PhotosController:wall'); 11 | $app->get('/subscribe','\Controllers\Users\PhotosController:subscribe'); 12 | $app->get('/','\Controllers\Users\PhotosController:wall'); 13 | $app->get('/weixin','\Controllers\Weixin\IndexController:vaild'); 14 | $app->get('/ttt','\Controllers\Weixin\IndexController:ttt'); 15 | $app->post('/weixin','\Controllers\Weixin\IndexController:index'); 16 | /* 17 | $app->get('/[{name}]', function (Request $request, Response $response, array $args) { 18 | // Sample log message 19 | $this->logger->info("Slim-Skeleton '/' route"); 20 | 21 | // Render index view 22 | return $this->renderer->render($response, 'index.phtml', $args); 23 | }); 24 | */ 25 | 26 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | 4 | # Some hosts may require you to use the `RewriteBase` directive. 5 | # Determine the RewriteBase automatically and set it as environment variable. 6 | # If you are using Apache aliases to do mass virtual hosting or installed the 7 | # project in a subdirectory, the base path will be prepended to allow proper 8 | # resolution of the index.php file and to redirect to the correct URI. It will 9 | # work in environments without path prefix as well, providing a safe, one-size 10 | # fits all solution. But as you do not need it in this case, you can comment 11 | # the following 2 lines to eliminate the overhead. 12 | RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$ 13 | RewriteRule ^(.*) - [E=BASE:%1] 14 | 15 | # If the above doesn't work you might need to set the `RewriteBase` directive manually, it should be the 16 | # absolute physical path to the directory that contains this htaccess file. 17 | # RewriteBase / 18 | 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [QSA,L] 21 | 22 | -------------------------------------------------------------------------------- /templates/index.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Slim 3 6 | 7 | 27 | 28 | 29 |

Slim

30 |
a microframework for PHP
31 | 32 | 33 |

Hello !

34 | 35 |

Try SlimFramework

36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/models/Qiniu.php: -------------------------------------------------------------------------------- 1 | accessKey = $config['access']; 23 | $this->secretKey = $config['secret']; 24 | $this->bucket = $config['bucket']; 25 | $this->url = $config['url']; 26 | 27 | } 28 | 29 | public function upload( $src ,$name = null){ 30 | $auth = new Auth($this->accessKey, $this->secretKey); 31 | $bucketManager = new BucketManager($auth); 32 | $url = $src ; 33 | $key = is_null( $name )? "pper_" . md5($src) . ".jpg": $name ; 34 | 35 | // 指定抓取的文件保存名称 36 | list($ret, $err) = $bucketManager->fetch($url, $this->bucket, $key); 37 | //echo "=====> fetch $url to bucket: $this->bucket key: $key\n"; 38 | if ($err !== null) { 39 | //var_dump($err); 40 | return false; 41 | } else { 42 | //print_r($ret); 43 | return $this->url . DIRECTORY_SEPARATOR . $ret['key']; 44 | } 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /tests/Functional/HomepageTest.php: -------------------------------------------------------------------------------- 1 | runApp('GET', '/'); 13 | 14 | $this->assertEquals(200, $response->getStatusCode()); 15 | $this->assertContains('SlimFramework', (string)$response->getBody()); 16 | $this->assertNotContains('Hello', (string)$response->getBody()); 17 | } 18 | 19 | /** 20 | * Test that the index route with optional name argument returns a rendered greeting 21 | */ 22 | public function testGetHomepageWithGreeting() 23 | { 24 | $response = $this->runApp('GET', '/name'); 25 | 26 | $this->assertEquals(200, $response->getStatusCode()); 27 | $this->assertContains('Hello name!', (string)$response->getBody()); 28 | } 29 | 30 | /** 31 | * Test that the index route won't accept a post request 32 | */ 33 | public function testPostHomepageNotAllowed() 34 | { 35 | $response = $this->runApp('POST', '/', ['test']); 36 | 37 | $this->assertEquals(405, $response->getStatusCode()); 38 | $this->assertContains('Method not allowed', (string)$response->getBody()); 39 | } 40 | } -------------------------------------------------------------------------------- /src/dependencies.php: -------------------------------------------------------------------------------- 1 | getContainer(); 5 | 6 | // view renderer 7 | $container['renderer'] = function ($c) { 8 | $settings = $c->get('settings')['renderer']; 9 | return new Slim\Views\PhpRenderer($settings['template_path']); 10 | }; 11 | 12 | $container['view'] = new \Slim\Views\PhpRenderer('../templates/'); 13 | 14 | // monolog 15 | $container['logger'] = function ($c) { 16 | $settings = $c->get('settings')['logger']; 17 | $logger = new Monolog\Logger($settings['name']); 18 | $logger->pushProcessor(new Monolog\Processor\UidProcessor()); 19 | $logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], $settings['level'])); 20 | return $logger; 21 | }; 22 | 23 | 24 | $c['errorHandler'] = function ($c) { 25 | return function ($request, $response, $exception) use ($c) { 26 | return $c['response']->withStatus(500) 27 | ->withHeader('Content-Type', 'text/html') 28 | ->write('Something went wrong!'); 29 | }; 30 | }; 31 | 32 | // PDO database library 33 | $container['db'] = function ($c) { 34 | $settings = $c->get('settings')['db']; 35 | $pdo = new PDO("mysql:host=" . $settings['host'] . ";dbname=" . $settings['dbname'], 36 | $settings['user'], $settings['pass']); 37 | $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 38 | $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); 39 | return $pdo; 40 | }; 41 | 42 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slim/slim-skeleton", 3 | "description": "A Slim Framework skeleton application for rapid development", 4 | "keywords": ["microframework", "rest", "router", "psr7"], 5 | "homepage": "http://github.com/slimphp/Slim-Skeleton", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Josh Lockhart", 10 | "email": "info@joshlockhart.com", 11 | "homepage": "http://www.joshlockhart.com/" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.5.0", 16 | "slim/slim": "^3.1", 17 | "slim/php-view": "^2.0", 18 | "monolog/monolog": "^1.17", 19 | "overtrue/wechat": "~4.1", 20 | "qiniu/php-sdk": "^7.2", 21 | "symfony/console": "^4.3" 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": ">=4.8 < 6.0" 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "Tests\\": "tests/", 29 | "Controllers\\": "src/controllers/", 30 | "Common\\": "src/common/", 31 | "Models\\": "src/models/", 32 | "Console\\": "src/console/" 33 | } 34 | }, 35 | "config": { 36 | "process-timeout" : 0 37 | }, 38 | "scripts": { 39 | "start": "php -S localhost:8080 -t public", 40 | "test": "phpunit" 41 | }, 42 | "repositories": { 43 | "packagist": { 44 | "type": "composer", 45 | "url": "https://packagist.phpcomposer.com" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # 拍拍客 2 | 基于微信互动的照片墙,使用slim3.0框架和easychat控件搭建 3 | 4 | # 效果 5 | Demo演示: http://www.pper.com.cn 6 | 7 | - 照片墙 8 | 9 | ![照片墙](https://github.com/liuhill/pper/blob/master/public/images/table.gif) 10 | 11 | - 水晶球 12 | 13 | ![水晶球](https://github.com/liuhill/pper/blob/master/public/images/sphere.gif) 14 | 15 | - 螺旋塔 16 | 17 | ![螺旋塔](https://github.com/liuhill/pper/blob/master/public/images/helix.gif) 18 | 19 | - 展览厅 20 | 21 | ![展览厅](https://github.com/liuhill/pper/blob/master/public/images/grid.gif) 22 | 23 | 24 | 25 | # 安装 26 | - 1 下载代码 27 | ``` 28 | git clone https://github.com/liuhill/pper.git 29 | ``` 30 | 31 | - 2 安装组件 32 | ``` 33 | composer install 34 | ``` 35 | 36 | - 3 生成自动加载 37 | ``` 38 | composer dump-autoload -o 39 | ``` 40 | 41 | 42 | - 4 创建mysql数据库`pper`,并导入数据表 43 | 44 | ``` 45 | mysql -uroot -p pper < db/pper.sql 46 | ``` 47 | 48 | - 5 复制配置文件`src/setting.default.php`为`src/setting.php` 49 | ``` 50 | cp src/setting.default.php src/setting.php 51 | ``` 52 | 53 | - 6 修改微信配置和数据库配置,文件 `src/setting.php` 54 | 55 | 微信 56 | ``` 57 | // weixin 58 | 'weixin' => [ 59 | 'app_id' => '', //app_id 60 | 'secret' => '', //EncodingAESKey 61 | 'token' => '', //token 62 | 63 | ], 64 | ``` 65 | mysql 66 | ``` 67 | // Database connection settings 68 | "db" => [ 69 | "host" => "localhost", 70 | "dbname" => "pper", 71 | "user" => "", 72 | "pass" => "" 73 | ], 74 | ``` 75 | 76 | 77 | - 4 启动服务 78 | 在根目录下执行 79 | ``` 80 | php -S 0.0.0.0:80 -t public 81 | ``` -------------------------------------------------------------------------------- /templates/subscribe.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 关注拍拍客 7 | 31 | 32 | 33 |

拍拍客

34 |
35 |
36 |
操作方法
37 | 42 |
43 |
44 | 源代码 45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /src/settings.defailt.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'displayErrorDetails' => true, // set to false in production 5 | 'addContentLengthHeader' => false, // Allow the web server to send the content-length header 6 | 7 | // Renderer settings 8 | 'renderer' => [ 9 | 'template_path' => __DIR__ . '/../templates/', 10 | ], 11 | 12 | // Monolog settings 13 | 'logger' => [ 14 | 'name' => 'slim-app', 15 | 'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/app.log', 16 | 'level' => \Monolog\Logger::DEBUG, 17 | ], 18 | 19 | // weixin 20 | 'weixin' => [ 21 | 'app_id' => '', //测试号 22 | 'secret' => '', 23 | 'token' => '', 24 | 'log' => [ 25 | 'level' => 'debug', 26 | 'file' => __DIR__ . '/../logs/weixin/easywechat.log', 27 | ] 28 | ], 29 | 30 | // 图片相关 31 | 'resource' => [ 32 | 'path' => __DIR__ . "/../public/resource", 33 | 'url' => "http://".$_SERVER['HTTP_HOST'] . "/resource", 34 | ], 35 | 36 | // Database connection settings 37 | "db" => [ 38 | "host" => "localhost", 39 | "dbname" => "pper", 40 | "user" => "", 41 | "pass" => "" 42 | ], 43 | // 七牛云 44 | 'qiniu' => [ 45 | 'enable' => false, 46 | 'access' => '', 47 | 'secret' => '', 48 | 'bucket' => '' 49 | ], 50 | 51 | ], 52 | ]; 53 | -------------------------------------------------------------------------------- /src/common/Curl/Curlex.php: -------------------------------------------------------------------------------- 1 | 错误信息] 23 | */ 24 | static public function get($url,$dest){ 25 | 26 | $ch = curl_init(); 27 | curl_setopt($ch, CURLOPT_URL, $url); 28 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 29 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); 30 | $file = curl_exec($ch); 31 | $errorInfo = self::valid(curl_errno($ch)); 32 | curl_close($ch); 33 | 34 | if($errorInfo === true){ 35 | //$filename = pathinfo($url, PATHINFO_BASENAME); 36 | $resource = fopen($dest, 'a'); 37 | fwrite($resource, $file); 38 | fclose($resource); 39 | return true; 40 | } 41 | else{ 42 | return $errorInfo; 43 | } 44 | 45 | } 46 | 47 | 48 | /** 49 | * @param $errorNo 50 | * @return array|bool 51 | * curl的错误信息处理 52 | * 如果为0则返回true 53 | * 否则:返回错误信息[错误代码=>错误信息] 54 | * 55 | */ 56 | static public function valid($errorNo){ 57 | if(!$errorNo){ 58 | return true; 59 | } 60 | $iniFile = __DIR__ . DIRECTORY_SEPARATOR . "error.ini"; 61 | if(file_exists($iniFile)){ 62 | $errorInfo = parse_ini_file($iniFile); 63 | $msg = $errorInfo[$errorNo]; 64 | return "$errorNo:$msg"; 65 | } 66 | else{ 67 | return "$errorNo:未知错误信息编码"; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/common/curl/Curlex.php: -------------------------------------------------------------------------------- 1 | 错误信息] 23 | */ 24 | static public function get($url,$dest){ 25 | 26 | $ch = curl_init(); 27 | curl_setopt($ch, CURLOPT_URL, $url); 28 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 29 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); 30 | $file = curl_exec($ch); 31 | $errorInfo = self::valid(curl_errno($ch)); 32 | curl_close($ch); 33 | 34 | if($errorInfo === true){ 35 | //$filename = pathinfo($url, PATHINFO_BASENAME); 36 | $resource = fopen($dest, 'a'); 37 | fwrite($resource, $file); 38 | fclose($resource); 39 | return true; 40 | } 41 | else{ 42 | return $errorInfo; 43 | } 44 | 45 | } 46 | 47 | 48 | /** 49 | * @param $errorNo 50 | * @return array|bool 51 | * curl的错误信息处理 52 | * 如果为0则返回true 53 | * 否则:返回错误信息[错误代码=>错误信息] 54 | * 55 | */ 56 | static public function valid($errorNo){ 57 | if(!$errorNo){ 58 | return true; 59 | } 60 | $iniFile = __DIR__ . DIRECTORY_SEPARATOR . "error.ini"; 61 | if(file_exists($iniFile)){ 62 | $errorInfo = parse_ini_file($iniFile); 63 | $msg = $errorInfo[$errorNo]; 64 | return "$errorNo:$msg"; 65 | } 66 | else{ 67 | return "$errorNo:未知错误信息编码"; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/console/Qn.php: -------------------------------------------------------------------------------- 1 | setName('app:qn') 34 | 35 | 36 | // the short description shown while running "php bin/console list" 37 | ->setDescription('Refresh latest post on Algolia dataset.') 38 | 39 | // the full command description shown when running the command with 40 | // the "--help" option 41 | ->setHelp('This command allows you refresh Algolia dataset with latest post.') 42 | 43 | /** 参数 44 | 45 | ->addArgument('name', InputArgument::REQUIRED, 'Who do you want to greet?') 46 | ->addArgument('last_name', InputArgument::OPTIONAL, 'Your last name?') 47 | //*/ 48 | ; 49 | 50 | } 51 | 52 | protected function execute(InputInterface $input, OutputInterface $output) 53 | { 54 | 55 | if( $this->qiniu['enable'] ) { 56 | $tbObj = new Message($this->db); 57 | $rows = $tbObj->getLocal(); 58 | foreach($rows as $row) { 59 | $name = basename($row['resource']); 60 | $uid = $row['uid']; 61 | $qn = new Qiniu($this->qiniu); 62 | $url = $qn->upload($row['resource'],"pper_uid$uid"."_".$name); 63 | if( $url !== false ) { 64 | $tbObj->updateUrl($url,$row['id']); 65 | $output->writeLn($row['id']."上传成功"); 66 | } 67 | } 68 | 69 | // Example code 70 | } 71 | 72 | else { 73 | $output->writeLn("上传七牛云设置为false"); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /public/wall2/stats.min.js: -------------------------------------------------------------------------------- 1 | // stats.js - http://github.com/mrdoob/stats.js 2 | var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; 3 | i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div"); 4 | k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display= 5 | "block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:11,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height= 6 | a+"px",m=b,r=0);return b},update:function(){l=this.end()}}}; -------------------------------------------------------------------------------- /src/common/Curl/error.ini: -------------------------------------------------------------------------------- 1 | ; 2 | ; HTTP curl error code info 3 | ; http://php.net/manual/en/curl.constants.php 4 | ; 5 | 6 | 1 = CURLE_UNSUPPORTED_PROTOCOL 7 | 2 = CURLE_FAILED_INIT 8 | 3 = CURLE_URL_MALFORMAT 9 | 4 = CURLE_URL_MALFORMAT_USER 10 | 5 = CURLE_COULDNT_RESOLVE_PROXY 11 | 6 = CURLE_COULDNT_RESOLVE_HOST 12 | 7 = CURLE_COULDNT_CONNECT 13 | 8 = CURLE_FTP_WEIRD_SERVER_REPLY 14 | 9 = CURLE_FTP_ACCESS_DENIED 15 | 10 = CURLE_FTP_USER_PASSWORD_INCORRECT 16 | 11 = CURLE_FTP_WEIRD_PASS_REPLY 17 | 12 = CURLE_FTP_WEIRD_USER_REPLY 18 | 13 = CURLE_FTP_WEIRD_PASV_REPLY 19 | 14 = CURLE_FTP_WEIRD_227_FORMAT 20 | 15 = CURLE_FTP_CANT_GET_HOST 21 | 16 = CURLE_FTP_CANT_RECONNECT 22 | 17 = CURLE_FTP_COULDNT_SET_BINARY 23 | 18 = CURLE_FTP_PARTIAL_FILE or CURLE_PARTIAL_FILE 24 | 19 = CURLE_FTP_COULDNT_RETR_FILE 25 | 20 = CURLE_FTP_WRITE_ERROR 26 | 21 = CURLE_FTP_QUOTE_ERROR 27 | 22 = CURLE_HTTP_NOT_FOUND or CURLE_HTTP_RETURNED_ERROR 28 | 23 = CURLE_WRITE_ERROR 29 | 24 = CURLE_MALFORMAT_USER 30 | 25 = CURLE_FTP_COULDNT_STOR_FILE 31 | 26 = CURLE_READ_ERROR 32 | 27 = CURLE_OUT_OF_MEMORY 33 | 28 = CURLE_OPERATION_TIMEDOUT or CURLE_OPERATION_TIMEOUTED 34 | 29 = CURLE_FTP_COULDNT_SET_ASCII 35 | 30 = CURLE_FTP_PORT_FAILED 36 | 31 = CURLE_FTP_COULDNT_USE_REST 37 | 32 = CURLE_FTP_COULDNT_GET_SIZE 38 | 33 = CURLE_HTTP_RANGE_ERROR 39 | 34 = CURLE_HTTP_POST_ERROR 40 | 35 = CURLE_SSL_CONNECT_ERROR 41 | 36 = CURLE_BAD_DOWNLOAD_RESUME or CURLE_FTP_BAD_DOWNLOAD_RESUME 42 | 37 = CURLE_FILE_COULDNT_READ_FILE 43 | 38 = CURLE_LDAP_CANNOT_BIND 44 | 39 = CURLE_LDAP_SEARCH_FAILED 45 | 40 = CURLE_LIBRARY_NOT_FOUND 46 | 41 = CURLE_FUNCTION_NOT_FOUND 47 | 42 = CURLE_ABORTED_BY_CALLBACK 48 | 43 = CURLE_BAD_FUNCTION_ARGUMENT 49 | 44 = CURLE_BAD_CALLING_ORDER 50 | 45 = CURLE_HTTP_PORT_FAILED 51 | 46 = CURLE_BAD_PASSWORD_ENTERED 52 | 47 = CURLE_TOO_MANY_REDIRECTS 53 | 48 = CURLE_UNKNOWN_TELNET_OPTION 54 | 49 = CURLE_TELNET_OPTION_SYNTAX 55 | 50 = CURLE_OBSOLETE 56 | 51 = CURLE_SSL_PEER_CERTIFICATE 57 | 52 = CURLE_GOT_NOTHING 58 | 53 = CURLE_SSL_ENGINE_NOTFOUND 59 | 54 = CURLE_SSL_ENGINE_SETFAILED 60 | 55 = CURLE_SEND_ERROR 61 | 56 = CURLE_RECV_ERROR 62 | 57 = CURLE_SHARE_IN_USE 63 | 58 = CURLE_SSL_CERTPROBLEM 64 | 59 = CURLE_SSL_CIPHER 65 | 60 = CURLE_SSL_CACERT 66 | 61 = CURLE_BAD_CONTENT_ENCODING 67 | 62 = CURLE_LDAP_INVALID_URL 68 | 63 = CURLE_FILESIZE_EXCEEDED 69 | 64 = CURLE_FTP_SSL_FAILED 70 | 79 = CURLE_SSH -------------------------------------------------------------------------------- /src/common/curl/error.ini: -------------------------------------------------------------------------------- 1 | ; 2 | ; HTTP curl error code info 3 | ; http://php.net/manual/en/curl.constants.php 4 | ; 5 | 6 | 1 = CURLE_UNSUPPORTED_PROTOCOL 7 | 2 = CURLE_FAILED_INIT 8 | 3 = CURLE_URL_MALFORMAT 9 | 4 = CURLE_URL_MALFORMAT_USER 10 | 5 = CURLE_COULDNT_RESOLVE_PROXY 11 | 6 = CURLE_COULDNT_RESOLVE_HOST 12 | 7 = CURLE_COULDNT_CONNECT 13 | 8 = CURLE_FTP_WEIRD_SERVER_REPLY 14 | 9 = CURLE_FTP_ACCESS_DENIED 15 | 10 = CURLE_FTP_USER_PASSWORD_INCORRECT 16 | 11 = CURLE_FTP_WEIRD_PASS_REPLY 17 | 12 = CURLE_FTP_WEIRD_USER_REPLY 18 | 13 = CURLE_FTP_WEIRD_PASV_REPLY 19 | 14 = CURLE_FTP_WEIRD_227_FORMAT 20 | 15 = CURLE_FTP_CANT_GET_HOST 21 | 16 = CURLE_FTP_CANT_RECONNECT 22 | 17 = CURLE_FTP_COULDNT_SET_BINARY 23 | 18 = CURLE_FTP_PARTIAL_FILE or CURLE_PARTIAL_FILE 24 | 19 = CURLE_FTP_COULDNT_RETR_FILE 25 | 20 = CURLE_FTP_WRITE_ERROR 26 | 21 = CURLE_FTP_QUOTE_ERROR 27 | 22 = CURLE_HTTP_NOT_FOUND or CURLE_HTTP_RETURNED_ERROR 28 | 23 = CURLE_WRITE_ERROR 29 | 24 = CURLE_MALFORMAT_USER 30 | 25 = CURLE_FTP_COULDNT_STOR_FILE 31 | 26 = CURLE_READ_ERROR 32 | 27 = CURLE_OUT_OF_MEMORY 33 | 28 = CURLE_OPERATION_TIMEDOUT or CURLE_OPERATION_TIMEOUTED 34 | 29 = CURLE_FTP_COULDNT_SET_ASCII 35 | 30 = CURLE_FTP_PORT_FAILED 36 | 31 = CURLE_FTP_COULDNT_USE_REST 37 | 32 = CURLE_FTP_COULDNT_GET_SIZE 38 | 33 = CURLE_HTTP_RANGE_ERROR 39 | 34 = CURLE_HTTP_POST_ERROR 40 | 35 = CURLE_SSL_CONNECT_ERROR 41 | 36 = CURLE_BAD_DOWNLOAD_RESUME or CURLE_FTP_BAD_DOWNLOAD_RESUME 42 | 37 = CURLE_FILE_COULDNT_READ_FILE 43 | 38 = CURLE_LDAP_CANNOT_BIND 44 | 39 = CURLE_LDAP_SEARCH_FAILED 45 | 40 = CURLE_LIBRARY_NOT_FOUND 46 | 41 = CURLE_FUNCTION_NOT_FOUND 47 | 42 = CURLE_ABORTED_BY_CALLBACK 48 | 43 = CURLE_BAD_FUNCTION_ARGUMENT 49 | 44 = CURLE_BAD_CALLING_ORDER 50 | 45 = CURLE_HTTP_PORT_FAILED 51 | 46 = CURLE_BAD_PASSWORD_ENTERED 52 | 47 = CURLE_TOO_MANY_REDIRECTS 53 | 48 = CURLE_UNKNOWN_TELNET_OPTION 54 | 49 = CURLE_TELNET_OPTION_SYNTAX 55 | 50 = CURLE_OBSOLETE 56 | 51 = CURLE_SSL_PEER_CERTIFICATE 57 | 52 = CURLE_GOT_NOTHING 58 | 53 = CURLE_SSL_ENGINE_NOTFOUND 59 | 54 = CURLE_SSL_ENGINE_SETFAILED 60 | 55 = CURLE_SEND_ERROR 61 | 56 = CURLE_RECV_ERROR 62 | 57 = CURLE_SHARE_IN_USE 63 | 58 = CURLE_SSL_CERTPROBLEM 64 | 59 = CURLE_SSL_CIPHER 65 | 60 = CURLE_SSL_CACERT 66 | 61 = CURLE_BAD_CONTENT_ENCODING 67 | 62 = CURLE_LDAP_INVALID_URL 68 | 63 = CURLE_FILESIZE_EXCEEDED 69 | 64 = CURLE_FTP_SSL_FAILED 70 | 79 = CURLE_SSH -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pper 2 | The 3D photo gallery , Built using Slim 3, Three.js, Tween.js, easychat 3 | 4 | # Demo 5 | ![WeChat](https://raw.githubusercontent.com/liuhill/pper/master/public/images/qrcode_for_gh_15b711976a8a_258.jpg) 6 | 7 | http://www.pper.com.cn 8 | 9 | 10 | - Wall Show 11 | 12 | ![Wall](https://raw.githubusercontent.com/liuhill/pper/master/screenshots/wall.jpg) 13 | 14 | - Cube Show 15 | 16 | ![Cube](https://raw.githubusercontent.com/liuhill/pper/master/screenshots/Cube.jpg) 17 | 18 | - Sphere Show 19 | 20 | ![Sphere](https://raw.githubusercontent.com/liuhill/pper/master/screenshots/Sphere.jpg) 21 | 22 | - Cylinder Show 23 | 24 | ![Cylinder](https://raw.githubusercontent.com/liuhill/pper/master/screenshots/Cylinder.jpg) 25 | 26 | - Plane 27 | 28 | ![Plane](https://raw.githubusercontent.com/liuhill/pper/master/screenshots/Plane.jpg) 29 | 30 | 31 | 32 | # Installation 33 | - 1、 Download source code 34 | ``` 35 | git clone https://github.com/liuhill/pper.git 36 | ``` 37 | 38 | - 2、 Install packages 39 | ``` 40 | composer install 41 | ``` 42 | 43 | - 3、 Generate autoload file 44 | ``` 45 | composer dump-autoload -o 46 | ``` 47 | 48 | 49 | - 4、 Create database `pper`, and import mysql table 50 | 51 | ``` 52 | mysql -uroot -p pper < db/pper.sql 53 | ``` 54 | 55 | - 5、 Copy Default Configure`src/setting.default.php`为`src/setting.php` 56 | ``` 57 | cp src/setting.default.php src/setting.php 58 | ``` 59 | 60 | - 6、 Configure `src/setting.php` 61 | 62 | Wechat 63 | ``` 64 | // weixin 65 | 'weixin' => [ 66 | 'app_id' => '', //app_id 67 | 'secret' => '', //EncodingAESKey 68 | 'token' => '', //token 69 | 70 | ], 71 | ``` 72 | 73 | mysql 74 | ``` 75 | // Database connection settings 76 | "db" => [ 77 | "host" => "localhost", 78 | "dbname" => "pper", 79 | "user" => "", 80 | "pass" => "" 81 | ], 82 | ``` 83 | 84 | qiniu cloud 85 | 86 | ``` 87 | // 七牛云 https://www.qiniu.com/ 88 | 'qiniu' => [ 89 | 'enable' => false, 90 | 'access' => '', 91 | 'secret' => '', 92 | 'bucket' => '' 93 | ], 94 | ``` 95 | 96 | 97 | - 7、 Start 98 | 99 | ``` 100 | php -S 0.0.0.0:80 -t public 101 | ``` 102 | OR 103 | ``` 104 | composer start 105 | ``` -------------------------------------------------------------------------------- /src/models/User.php: -------------------------------------------------------------------------------- 1 | con = $db; 19 | } 20 | 21 | 22 | public function validUser($id){ 23 | $sql = "SELECT * FROM `user` WHERE `id`=$id"; 24 | $row = $this->con->query($sql)->fetchAll(); 25 | if(count($row)>0){ 26 | return $id; 27 | } 28 | return false; 29 | } 30 | 31 | public function getUid($user){ 32 | $isMob='/^1[34578]{1}\d{9}$/'; 33 | $isEmail = '/^([\.a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/'; 34 | if(preg_match($isMob,$user)){ //手机号 35 | echo '手机号用户'; 36 | 37 | } 38 | elseif(preg_match($isEmail,$user)) //邮箱 39 | { 40 | echo '邮箱用户'; 41 | } 42 | else{ //openid或者nickname 43 | $sql = "SELECT * FROM `user` WHERE `nickname`='$user' OR `openid`='$user'"; 44 | $row = $this->con->query($sql)->fetchAll(); 45 | if(count($row)>0){ 46 | return intval($row[0]['id']); 47 | } 48 | return false; 49 | } 50 | 51 | return false; 52 | } 53 | 54 | public function getUidByOpenid($openId){ 55 | $uid = $this->getUid($openId); 56 | if($uid === false){ 57 | $uid = $this->addUser("weixin",$openId); 58 | } 59 | return $uid; 60 | } 61 | 62 | public function addUser($type,$info){ 63 | 64 | $ll = $ct = date("Y-m-d H:i:s"); 65 | $db=$this->con; 66 | switch($type){ 67 | case 'weixin': 68 | $sql = "INSERT INTO `user`(`openid`,`create_time`,`last_login`) VALUES(:openid,:createtime,:lasttime)"; 69 | $stmt = $db->prepare($sql); 70 | $stmt->bindParam(":openid",$info); 71 | $stmt->bindParam(":createtime",$ct); 72 | $stmt->bindParam(":lasttime",$ll); 73 | $stmt->execute(); 74 | $id = $db->lastInsertId(); 75 | break; 76 | case 'mobile': 77 | break; 78 | case 'email': 79 | break; 80 | default: 81 | break; 82 | } 83 | return $id; 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/models/Message.php: -------------------------------------------------------------------------------- 1 | con = $db; 20 | } 21 | 22 | public function getLastMessage(){ 23 | 24 | } 25 | 26 | 27 | public function getLocal(){ 28 | $sql = "SELECT * FROM message WHERE resource LIKE \"http://www.pper.com.cn%\""; 29 | return $this->con->query($sql)->fetchAll(); 30 | } 31 | 32 | 33 | public function getImages($uid) { 34 | $rows = $this->getMessage($uid); 35 | if(count($rows) < Message::COUNT){ 36 | $fillRows = $this->getMessage(1,Message::COUNT - count($rows)); 37 | $rows = array_merge($rows,$fillRows); 38 | } 39 | 40 | return $rows; 41 | } 42 | 43 | public function getMessage($uid,$count = Message::COUNT){ 44 | 45 | $order = " ORDER BY create_time DESC limit " . $count; 46 | if(is_null($uid)) { 47 | $sql = "SELECT * FROM message " . $order; 48 | } 49 | else { 50 | $sql = "SELECT * FROM message WHERE uid=$uid " . $order; 51 | } 52 | 53 | //$db->prepare($sql); 54 | //$db->bindParam(":uid",$uid); 55 | 56 | return $this->con->query($sql)->fetchAll(); 57 | } 58 | 59 | public function updateUrl($url,$id) { 60 | $sql = "UPDATE message SET resource=:resource WHERE id=:id"; 61 | $stmt = $this->con->prepare($sql); 62 | $stmt->bindParam(":resource", $url); 63 | $stmt->bindParam(":id", $id); 64 | $res = $stmt->execute(); 65 | return $res; 66 | } 67 | 68 | public function saveMessage($uid,$type,$content,$resource){ 69 | $curData = date("Y-m-d H:i:s"); 70 | $db = $this->con; 71 | $sql = "INSERT INTO `message`(`uid`,`type`,`content`,`resource`,`create_time`) VALUES (:uid,:resouceType,:content,:resource,:createtime)"; 72 | $stmt = $db->prepare($sql); 73 | $stmt->bindParam(":uid", $uid); 74 | $stmt->bindParam(":resouceType", $type); 75 | $stmt->bindParam(":content", $content); 76 | $stmt->bindParam(":resource", $resource); 77 | $stmt->bindParam(":createtime", $curData); 78 | $res = $stmt->execute(); 79 | return $res; 80 | } 81 | } -------------------------------------------------------------------------------- /tests/Functional/BaseTestCase.php: -------------------------------------------------------------------------------- 1 | $requestMethod, 39 | 'REQUEST_URI' => $requestUri 40 | ] 41 | ); 42 | 43 | // Set up a request object based on the environment 44 | $request = Request::createFromEnvironment($environment); 45 | 46 | // Add request data, if it exists 47 | if (isset($requestData)) { 48 | $request = $request->withParsedBody($requestData); 49 | } 50 | 51 | // Set up a response object 52 | $response = new Response(); 53 | 54 | // Use the application settings 55 | $settings = require __DIR__ . '/../../src/settings.php'; 56 | 57 | // Instantiate the application 58 | $app = new App($settings); 59 | 60 | // Set up dependencies 61 | require __DIR__ . '/../../src/dependencies.php'; 62 | 63 | // Register middleware 64 | if ($this->withMiddleware) { 65 | require __DIR__ . '/../../src/middleware.php'; 66 | } 67 | 68 | // Register routes 69 | require __DIR__ . '/../../src/routes.php'; 70 | 71 | // Process the application 72 | $response = $app->process($request, $response); 73 | 74 | // Return the response 75 | return $response; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /public/css/lightbox.min.css: -------------------------------------------------------------------------------- 1 | .lb-loader,.lightbox{text-align:center;line-height:0}.lb-dataContainer:after,.lb-outerContainer:after{content:"";clear:both}html.lb-disable-scrolling{overflow:hidden;position:fixed;height:100vh;width:100vw}.lightboxOverlay{position:absolute;top:0;left:0;z-index:9999;background-color:#000;filter:alpha(Opacity=80);opacity:.8;display:none}.lightbox{position:absolute;left:0;width:100%;z-index:10000;font-weight:400}.lightbox .lb-image{display:block;height:auto;max-width:inherit;max-height:none;border-radius:3px;border:4px solid #fff}.lightbox a img{border:none}.lb-outerContainer{position:relative;width:250px;height:250px;margin:0 auto;border-radius:4px;background-color:#fff}.lb-loader,.lb-nav{position:absolute;left:0}.lb-outerContainer:after{display:table}.lb-loader{top:43%;height:25%;width:100%}.lb-cancel{display:block;width:32px;height:32px;margin:0 auto;background:url(../images/loading.gif) no-repeat}.lb-nav{top:0;height:100%;width:100%;z-index:10}.lb-container>.nav{left:0}.lb-nav a{outline:0;background-image:url(data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==)}.lb-next,.lb-prev{height:100%;cursor:pointer;display:block}.lb-nav a.lb-prev{width:34%;left:0;float:left;background:url(../images/prev.png) left 48% no-repeat;filter:alpha(Opacity=0);opacity:0;-webkit-transition:opacity .6s;-moz-transition:opacity .6s;-o-transition:opacity .6s;transition:opacity .6s}.lb-nav a.lb-prev:hover{filter:alpha(Opacity=100);opacity:1}.lb-nav a.lb-next{width:64%;right:0;float:right;background:url(../images/next.png) right 48% no-repeat;filter:alpha(Opacity=0);opacity:0;-webkit-transition:opacity .6s;-moz-transition:opacity .6s;-o-transition:opacity .6s;transition:opacity .6s}.lb-nav a.lb-next:hover{filter:alpha(Opacity=100);opacity:1}.lb-dataContainer{margin:0 auto;padding-top:5px;width:100%;border-bottom-left-radius:4px;border-bottom-right-radius:4px}.lb-dataContainer:after{display:table}.lb-data{padding:0 4px;color:#ccc}.lb-data .lb-details{width:85%;float:left;text-align:left;line-height:1.1em}.lb-data .lb-caption{font-size:13px;font-weight:700;line-height:1em}.lb-data .lb-caption a{color:#4ae}.lb-data .lb-number{display:block;clear:left;padding-bottom:1em;font-size:12px;color:#999}.lb-data .lb-close{display:block;float:right;width:30px;height:30px;background:url(../images/close.png) top right no-repeat;text-align:right;outline:0;filter:alpha(Opacity=70);opacity:.7;-webkit-transition:opacity .2s;-moz-transition:opacity .2s;-o-transition:opacity .2s;transition:opacity .2s}.lb-data .lb-close:hover{cursor:pointer;filter:alpha(Opacity=100);opacity:1} -------------------------------------------------------------------------------- /templates/wall.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 拍拍客 5 | 6 | 7 | 8 | 9 | 15 | 16 | 17 |
18 | 26 | 27 | 28 |
29 | 我们喜欢随手拍一拍,偶尔会看一看。我们是拍拍客! 30 |
31 |
32 |
33 | 39 | 40 | 41 |
42 | 43 |
44 | 使用方法: 45 | 50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 源代码 60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 73 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /db/pper.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.16 Distrib 10.3.10-MariaDB, for osx10.14 (x86_64) 2 | -- 3 | -- Host: localhost Database: pper 4 | -- ------------------------------------------------------ 5 | -- Server version 10.3.10-MariaDB 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `message` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `message`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `message` ( 26 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 27 | `uid` int(11) NOT NULL COMMENT '用户id', 28 | `type` varchar(16) NOT NULL COMMENT '类型', 29 | `content` varchar(256) DEFAULT NULL COMMENT '文字内容', 30 | `resource` varchar(256) DEFAULT NULL COMMENT '资源路径', 31 | `create_time` datetime NOT NULL COMMENT '创建时间', 32 | PRIMARY KEY (`id`), 33 | KEY `uid` (`uid`), 34 | KEY `createdtime` (`create_time`) 35 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; 36 | /*!40101 SET character_set_client = @saved_cs_client */; 37 | 38 | -- 39 | -- Table structure for table `user` 40 | -- 41 | 42 | DROP TABLE IF EXISTS `user`; 43 | /*!40101 SET @saved_cs_client = @@character_set_client */; 44 | /*!40101 SET character_set_client = utf8 */; 45 | CREATE TABLE `user` ( 46 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 47 | `nickname` varchar(32) DEFAULT NULL COMMENT '昵称', 48 | `password` varchar(128) DEFAULT NULL COMMENT '密码', 49 | `mobile` varchar(32) DEFAULT NULL COMMENT '手机号', 50 | `email` varchar(64) DEFAULT NULL COMMENT '邮箱地址', 51 | `openid` varchar(128) NOT NULL COMMENT '微信在公众账号的openid', 52 | `create_time` datetime NOT NULL COMMENT '注册时间', 53 | `last_login` datetime NOT NULL COMMENT '上次登陆时间', 54 | PRIMARY KEY (`id`), 55 | UNIQUE KEY `nickname_UNIQUE` (`nickname`) 56 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; 57 | /*!40101 SET character_set_client = @saved_cs_client */; 58 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 59 | 60 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 61 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 62 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 63 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 64 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 65 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 66 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 67 | 68 | -- Dump completed on 2019-01-07 13:56:22 69 | -------------------------------------------------------------------------------- /src/controllers/Users/PhotosController.php: -------------------------------------------------------------------------------- 1 | container = $container; 24 | $this->view = $container->get('view'); 25 | $this->resource = $container->get("settings")['resource']; 26 | $this->db = $container->get("db"); 27 | } 28 | 29 | private function getImagesByUid($uid){ 30 | $imgDir = $this->photoDir['resize'] . DIRECTORY_SEPARATOR . $uid; 31 | $imgUrl = $this->photoDir['resizeUrl'] . DIRECTORY_SEPARATOR . $uid; 32 | $originalUrl = $this->photoDir['originalUrl'] . DIRECTORY_SEPARATOR . $uid; 33 | $images = Photo::list($imgDir); 34 | foreach($images as $i=>$v){ 35 | $images[$i]['url'] = $imgUrl .DIRECTORY_SEPARATOR .$v['name']; 36 | $images[$i]['original'] = $originalUrl .DIRECTORY_SEPARATOR .$v['name']; 37 | } 38 | //echo $images; 39 | return $images; 40 | } 41 | 42 | public function index($request, $response, $args){ 43 | // $images = $this->getImagesByUid($uid); 44 | 45 | 46 | if(isset($args['id']) && !empty($args['id'])){ 47 | $uid = intval($args['id']); 48 | } 49 | $mMessage = new Message($this->db); 50 | if (empty($uid)) { 51 | $resouce = $mMessage->getImages(1); 52 | } else { 53 | $resouce = $mMessage->getImages($uid); 54 | } 55 | $data = []; 56 | foreach($resouce as $i=>$v){ 57 | $info = []; 58 | $info['unique_id'] = $v['id']; 59 | $info['type'] = $type = $v['type']; 60 | switch($type){ 61 | case 'text': 62 | $info['text'] = $v['content']; 63 | break; 64 | case 'image': 65 | case 'voice': 66 | case 'video': 67 | case 'file': 68 | $info['src'] = $v['content']; 69 | $info['resource'] = $v['resource']; 70 | break; 71 | default: 72 | continue; 73 | } 74 | $data[] = $info; 75 | } 76 | 77 | return $response->withJson($data); 78 | } 79 | 80 | public function wall($request, $response, $args){ 81 | 82 | if(isset($args['id']) && !empty($args['id'])){ 83 | $uid = $args['id']; 84 | $mUser = new User($this->db); 85 | $uid = $mUser->validUser($uid); 86 | if($uid === false) { 87 | $uid = ''; 88 | } 89 | } 90 | else { 91 | $uid = ''; 92 | } 93 | 94 | $response = $this->view->render($response, 95 | 'wall2.phtml', 96 | [ 97 | 'uid' => $uid 98 | ]); 99 | return $response; 100 | 101 | } 102 | 103 | public function subscribe($request, $response, $args){ 104 | $response = $this->view->render($response, 105 | 'subscribe.phtml'); 106 | return $response; 107 | } 108 | } -------------------------------------------------------------------------------- /public/css/wall.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | background-color: #000000; 7 | margin: 0; 8 | font-family: Helvetica, sans-serif;; 9 | overflow: hidden; 10 | } 11 | 12 | #info { 13 | position: absolute; 14 | width: 100%; 15 | padding: 5px; 16 | font-family: Monospace; 17 | font-size: 1em; 18 | font-weight: bold; 19 | text-align: center; 20 | z-index: 1; 21 | } 22 | 23 | #info a { 24 | text-decoration:none; 25 | color: #d4d2d2; 26 | } 27 | 28 | #info span { 29 | font-size: 1em; 30 | color: deepskyblue; 31 | } 32 | 33 | #info ul li { 34 | display:inline; 35 | } 36 | 37 | #info li { 38 | border-right: 1px solid white; 39 | margin-left: 0.4em; 40 | } 41 | 42 | #info li:first-child a { 43 | color: white; 44 | } 45 | 46 | #info li:last-child { 47 | border-right: 0px; 48 | } 49 | 50 | 51 | #info .slogon{ 52 | color: #9a9898; 53 | font-size: .8em; 54 | } 55 | 56 | 57 | #weixin { 58 | font-size: 1em; 59 | color: white; 60 | margin-bottom: 1em; 61 | } 62 | 63 | 64 | #weixin ul{ 65 | list-style-type:none; 66 | margin:0px; 67 | padding:0px; 68 | font-size: .8em; 69 | } 70 | 71 | #weixin img { 72 | width: 130px; 73 | height: 130px; 74 | } 75 | 76 | #demo { 77 | /*text-align: center;*/ 78 | color: white; 79 | margin-bottom: 1em; 80 | } 81 | 82 | #demo img { 83 | width: 110px; 84 | height: 80px; 85 | opacity: 0.6; 86 | filter:alpha(opacity=60); /* 针对 IE8 以及更早的版本 */ 87 | } 88 | 89 | 90 | #menu { 91 | position: absolute; 92 | bottom: 20px; 93 | width: 100%; 94 | text-align: center; 95 | font-size: 1em; 96 | } 97 | 98 | #code { 99 | z-index: 1; 100 | position: absolute; 101 | font-size: 0.5em; 102 | } 103 | #code a { 104 | color: red; 105 | } 106 | 107 | .element { 108 | width: 161px; 109 | height: 100px; 110 | box-shadow: 0px 0px 12px rgba(0,255,255,0.5); 111 | text-align: center; 112 | border: 1px solid rgba(127,255,255,0.25); 113 | cursor: default; 114 | } 115 | 116 | .element:hover { 117 | box-shadow: 0px 0px 12px rgba(0,255,255,0.75); 118 | border: 1px solid rgba(127,255,255,0.75); 119 | } 120 | 121 | 122 | .element img { 123 | 124 | margin: 10px; 125 | object-fit: none; /* Do not scale the image */ 126 | object-position: center; /* Center the image within the element */ 127 | width: 141px; 128 | height: 80px; 129 | } 130 | 131 | .element .number { 132 | position: absolute; 133 | top: 20px; 134 | right: 20px; 135 | font-size: 12px; 136 | color: rgba(127,255,255,0.75); 137 | } 138 | 139 | .element .symbol { 140 | position: absolute; 141 | top: 40px; 142 | left: 0px; 143 | right: 0px; 144 | font-size: 60px; 145 | font-weight: bold; 146 | color: rgba(255,255,255,0.75); 147 | text-shadow: 0 0 10px rgba(0,255,255,0.95); 148 | } 149 | 150 | .element .details { 151 | position: absolute; 152 | bottom: 15px; 153 | left: 0px; 154 | right: 0px; 155 | font-size: 12px; 156 | color: rgba(127,255,255,0.75); 157 | } 158 | 159 | 160 | button { 161 | color: rgba(127,255,255,0.75); 162 | background: transparent; 163 | outline: 1px solid rgba(127,255,255,0.75); 164 | border: 0px; 165 | padding: 5px 10px; 166 | cursor: pointer; 167 | } 168 | button:hover { 169 | background-color: rgba(0,255,255,0.5); 170 | } 171 | button:active { 172 | color: #000000; 173 | background-color: rgba(0,255,255,0.75); 174 | } 175 | 176 | 177 | @media screen and (max-width: 800px) { /*当屏幕尺寸小于600px时,应用下面的CSS样式*/ 178 | #weixin { 179 | display: none; 180 | } 181 | 182 | #demo { 183 | display: none; 184 | } 185 | 186 | #code { 187 | display: none; 188 | } 189 | } 190 | 191 | -------------------------------------------------------------------------------- /public/css/wall2.css: -------------------------------------------------------------------------------- 1 | body { 2 | /* set margin to 0 and overflow to hidden, to go fullscreen */ 3 | margin: 0; 4 | overflow: hidden; 5 | background-color: black; 6 | } 7 | 8 | #someidentifier { 9 | position: fixed; 10 | z-index: 100; 11 | bottom: 0; 12 | left: 0; 13 | width: 100%; 14 | background-color: black; 15 | opacity: 0.7; 16 | color: #ffffff; 17 | font-family: Calibri; 18 | } 19 | 20 | #query { 21 | margin-left: 5px; 22 | } 23 | 24 | #info { 25 | position: absolute; 26 | width: 100%; 27 | padding: 5px; 28 | font-family: Monospace; 29 | font-size: 1em; 30 | font-weight: bold; 31 | text-align: center; 32 | z-index: 1; 33 | } 34 | 35 | #info a { 36 | text-decoration:none; 37 | color: #d4d2d2; 38 | } 39 | 40 | #info span { 41 | font-size: 1em; 42 | color: deepskyblue; 43 | } 44 | 45 | #info ul li { 46 | display:inline; 47 | } 48 | 49 | #info li { 50 | border-right: 1px solid white; 51 | margin-left: 0.4em; 52 | } 53 | 54 | #info li:first-child a { 55 | color: white; 56 | } 57 | 58 | #info li:last-child { 59 | border-right: 0px; 60 | } 61 | 62 | 63 | #info .slogon{ 64 | color: #9a9898; 65 | font-size: .8em; 66 | } 67 | 68 | 69 | #weixin { 70 | font-size: 1em; 71 | color: white; 72 | position: fixed; 73 | right:0rem; 74 | bottom: 1rem; 75 | margin: 1rem; 76 | } 77 | 78 | 79 | #weixin ul{ 80 | list-style-type:none; 81 | margin:0px; 82 | padding:0px; 83 | font-size: .8em; 84 | } 85 | 86 | #weixin img { 87 | width: 130px; 88 | height: 130px; 89 | } 90 | 91 | #demo { 92 | /*text-align: center;*/ 93 | color: white; 94 | margin-bottom: 1em; 95 | } 96 | 97 | #demo img { 98 | width: 110px; 99 | height: 80px; 100 | opacity: 0.6; 101 | filter:alpha(opacity=60); /* 针对 IE8 以及更早的版本 */ 102 | } 103 | 104 | 105 | #menu { 106 | position: absolute; 107 | bottom: 20px; 108 | width: 100%; 109 | text-align: center; 110 | font-size: 1em; 111 | } 112 | 113 | #code { 114 | z-index: 1; 115 | position: absolute; 116 | font-size: 0.5em; 117 | } 118 | #code a { 119 | color: red; 120 | } 121 | 122 | .element { 123 | width: 161px; 124 | height: 100px; 125 | box-shadow: 0px 0px 12px rgba(0,255,255,0.5); 126 | text-align: center; 127 | border: 1px solid rgba(127,255,255,0.25); 128 | cursor: default; 129 | } 130 | 131 | .element:hover { 132 | box-shadow: 0px 0px 12px rgba(0,255,255,0.75); 133 | border: 1px solid rgba(127,255,255,0.75); 134 | } 135 | 136 | 137 | .element img { 138 | 139 | margin: 10px; 140 | object-fit: none; /* Do not scale the image */ 141 | object-position: center; /* Center the image within the element */ 142 | width: 141px; 143 | height: 80px; 144 | } 145 | 146 | .element .number { 147 | position: absolute; 148 | top: 20px; 149 | right: 20px; 150 | font-size: 12px; 151 | color: rgba(127,255,255,0.75); 152 | } 153 | 154 | .element .symbol { 155 | position: absolute; 156 | top: 40px; 157 | left: 0px; 158 | right: 0px; 159 | font-size: 60px; 160 | font-weight: bold; 161 | color: rgba(255,255,255,0.75); 162 | text-shadow: 0 0 10px rgba(0,255,255,0.95); 163 | } 164 | 165 | .element .details { 166 | position: absolute; 167 | bottom: 15px; 168 | left: 0px; 169 | right: 0px; 170 | font-size: 12px; 171 | color: rgba(127,255,255,0.75); 172 | } 173 | 174 | 175 | button { 176 | color: rgba(127,255,255,0.75); 177 | background: transparent; 178 | outline: 1px solid rgba(127,255,255,0.75); 179 | border: 0px; 180 | padding: 5px 10px; 181 | cursor: pointer; 182 | } 183 | button:hover { 184 | background-color: rgba(0,255,255,0.5); 185 | } 186 | button:active { 187 | color: #000000; 188 | background-color: rgba(0,255,255,0.75); 189 | } 190 | 191 | 192 | @media screen and (max-width: 800px) { /*当屏幕尺寸小于600px时,应用下面的CSS样式*/ 193 | #weixin { 194 | display: none; 195 | } 196 | 197 | #demo { 198 | display: none; 199 | } 200 | 201 | #code { 202 | display: none; 203 | } 204 | } 205 | 206 | -------------------------------------------------------------------------------- /public/js/tween.min.js: -------------------------------------------------------------------------------- 1 | // tween.js - http://github.com/sole/tween.js 2 | 'use strict';var TWEEN=TWEEN||function(){var a=[];return{REVISION:"7",getAll:function(){return a},removeAll:function(){a=[]},add:function(c){a.push(c)},remove:function(c){c=a.indexOf(c);-1!==c&&a.splice(c,1)},update:function(c){if(0===a.length)return!1;for(var b=0,d=a.length,c=void 0!==c?c:Date.now();b(a*=2)?0.5*a*a:-0.5*(--a*(a-2)-1)}},Cubic:{In:function(a){return a*a*a},Out:function(a){return--a*a*a+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*a:0.5*((a-=2)*a*a+2)}},Quartic:{In:function(a){return a*a*a*a},Out:function(a){return 1- --a*a*a*a},InOut:function(a){return 1>(a*=2)?0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)}},Quintic:{In:function(a){return a*a*a* 7 | a*a},Out:function(a){return--a*a*a*a*a+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)}},Sinusoidal:{In:function(a){return 1-Math.cos(a*Math.PI/2)},Out:function(a){return Math.sin(a*Math.PI/2)},InOut:function(a){return 0.5*(1-Math.cos(Math.PI*a))}},Exponential:{In:function(a){return 0===a?0:Math.pow(1024,a-1)},Out:function(a){return 1===a?1:1-Math.pow(2,-10*a)},InOut:function(a){return 0===a?0:1===a?1:1>(a*=2)?0.5*Math.pow(1024,a-1):0.5*(-Math.pow(2,-10*(a-1))+2)}},Circular:{In:function(a){return 1- 8 | Math.sqrt(1-a*a)},Out:function(a){return Math.sqrt(1- --a*a)},InOut:function(a){return 1>(a*=2)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)}},Elastic:{In:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return-(b*Math.pow(2,10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4))},Out:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return b*Math.pow(2,-10*a)*Math.sin((a-c)* 9 | 2*Math.PI/0.4)+1},InOut:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return 1>(a*=2)?-0.5*b*Math.pow(2,10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4):0.5*b*Math.pow(2,-10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4)+1}},Back:{In:function(a){return a*a*(2.70158*a-1.70158)},Out:function(a){return--a*a*(2.70158*a+1.70158)+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*(3.5949095*a-2.5949095):0.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)}},Bounce:{In:function(a){return 1- 10 | TWEEN.Easing.Bounce.Out(1-a)},Out:function(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375},InOut:function(a){return 0.5>a?0.5*TWEEN.Easing.Bounce.In(2*a):0.5*TWEEN.Easing.Bounce.Out(2*a-1)+0.5}}}; 11 | TWEEN.Interpolation={Linear:function(a,c){var b=a.length-1,d=b*c,e=Math.floor(d),f=TWEEN.Interpolation.Utils.Linear;return 0>c?f(a[0],a[1],d):1b?b:e+1],d-e)},Bezier:function(a,c){var b=0,d=a.length-1,e=Math.pow,f=TWEEN.Interpolation.Utils.Bernstein,h;for(h=0;h<=d;h++)b+=e(1-c,d-h)*e(c,h)*a[h]*f(d,h);return b},CatmullRom:function(a,c){var b=a.length-1,d=b*c,e=Math.floor(d),f=TWEEN.Interpolation.Utils.CatmullRom;return a[0]===a[b]?(0>c&&(e=Math.floor(d=b*(1+c))),f(a[(e- 12 | 1+b)%b],a[e],a[(e+1)%b],a[(e+2)%b],d-e)):0>c?a[0]-(f(a[0],a[0],a[1],a[1],-d)-a[0]):1$v){ 38 | $size[$k] = $v['size']; 39 | $time[$k] = $v['time']; 40 | $name[$k] = $v['name']; 41 | } 42 | array_multisort($time,SORT_DESC,SORT_STRING, $files);//按时间排序 43 | //array_multisort($name,SORT_DESC,SORT_STRING, $files);//按名字排序 44 | //array_multisort($size,SORT_DESC,SORT_NUMERIC, $files);//按大小排序 45 | } 46 | return $files; 47 | } 48 | 49 | 50 | static public function getFormate($url){ 51 | $imageTypeArray = array 52 | ( 53 | 0=>'UNKNOWN', 54 | 1=>'GIF', 55 | 2=>'JPEG', 56 | 3=>'PNG', 57 | 4=>'SWF', 58 | 5=>'PSD', 59 | 6=>'BMP', 60 | 7=>'TIFF_II', 61 | 8=>'TIFF_MM', 62 | 9=>'JPC', 63 | 10=>'JP2', 64 | 11=>'JPX', 65 | 12=>'JB2', 66 | 13=>'SWC', 67 | 14=>'IFF', 68 | 15=>'WBMP', 69 | 16=>'XBM', 70 | 17=>'ICO', 71 | 18=>'COUNT' 72 | ); 73 | $size = getimagesize($url); 74 | return $imageTypeArray[$size[2]]; 75 | } 76 | 77 | /** 78 | * 图片下载 79 | * @param $url 80 | * @param $dir 81 | * @return array|bool|string 82 | */ 83 | static public function download($url,$dir){ 84 | 85 | if(!is_dir($dir)){ 86 | mkdir($dir,0775,true); 87 | } 88 | //去除URL连接上面可能的引号 89 | $url = preg_replace( '/(?:^[\'"]+|[\'"\/]+$)/', '', $url ); 90 | 91 | $ext = self::getFormate($url); 92 | list($msec,$sec) = explode ( " ", microtime () ); 93 | $seq = str_replace('0.','~',$msec); 94 | $filename = date('Ymd~His',$sec). "$seq.$ext"; 95 | 96 | $originalFile = $dir . DIRECTORY_SEPARATOR . $filename; 97 | $res = Curlex::get($url,$originalFile); 98 | if($res === true){ 99 | return $originalFile; 100 | } 101 | else{ 102 | return $res; 103 | } 104 | } 105 | 106 | 107 | /** 108 | * 图片缩放 109 | * @param $src : 源图片路径 110 | * @param $dest : 目标图片路径 111 | * @param array $size : 图片的 [宽度,高度] 112 | * @return bool 113 | */ 114 | static public function resize($src,$dest,$size = [120,160]){ 115 | 116 | if(!file_exists($src)){ 117 | return "源文件不存在: $src"; 118 | } 119 | 120 | $destDir = dirname($dest); 121 | if(!is_dir($destDir)) 122 | { 123 | mkdir($destDir,0775,true); 124 | } 125 | 126 | list($distWidth,$distHeight) = $size; 127 | 128 | list($width, $height) = getimagesize($src); 129 | $percent = 1; 130 | if($width /$height >= $distWidth/$distHeight) 131 | { 132 | 133 | if($width > $distWidth) 134 | { 135 | $percent = $distHeight/$height; 136 | } 137 | } 138 | else 139 | { 140 | if($height > $distHeight) 141 | { 142 | $percent = $distWidth/$width; 143 | } 144 | } 145 | $new_width = $width * $percent; 146 | $new_height = $height * $percent; 147 | //创建新的图片此图片的标志为$image_p 148 | $image_p = imagecreatetruecolor($new_width, $new_height); 149 | $image = imagecreatefromjpeg($src); 150 | imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height); 151 | 152 | // Output 153 | imagejpeg($image_p, $dest, 100);//quality为图片输出的质量范围从 0(最差质量,文件更小)到 100(最佳质量,文件最大)。 154 | 155 | return true; 156 | } 157 | 158 | } -------------------------------------------------------------------------------- /src/common/image/Photo.php: -------------------------------------------------------------------------------- 1 | $v){ 38 | $size[$k] = $v['size']; 39 | $time[$k] = $v['time']; 40 | $name[$k] = $v['name']; 41 | } 42 | array_multisort($time,SORT_DESC,SORT_STRING, $files);//按时间排序 43 | //array_multisort($name,SORT_DESC,SORT_STRING, $files);//按名字排序 44 | //array_multisort($size,SORT_DESC,SORT_NUMERIC, $files);//按大小排序 45 | } 46 | return $files; 47 | } 48 | 49 | 50 | static public function getFormate($url){ 51 | $imageTypeArray = array 52 | ( 53 | 0=>'UNKNOWN', 54 | 1=>'GIF', 55 | 2=>'JPEG', 56 | 3=>'PNG', 57 | 4=>'SWF', 58 | 5=>'PSD', 59 | 6=>'BMP', 60 | 7=>'TIFF_II', 61 | 8=>'TIFF_MM', 62 | 9=>'JPC', 63 | 10=>'JP2', 64 | 11=>'JPX', 65 | 12=>'JB2', 66 | 13=>'SWC', 67 | 14=>'IFF', 68 | 15=>'WBMP', 69 | 16=>'XBM', 70 | 17=>'ICO', 71 | 18=>'COUNT' 72 | ); 73 | $size = getimagesize($url); 74 | return $imageTypeArray[$size[2]]; 75 | } 76 | 77 | /** 78 | * 图片下载 79 | * @param $url 80 | * @param $dir 81 | * @return array|bool|string 82 | */ 83 | static public function download($url,$dir){ 84 | 85 | if(!is_dir($dir)){ 86 | mkdir($dir,0775,true); 87 | } 88 | //去除URL连接上面可能的引号 89 | $url = preg_replace( '/(?:^[\'"]+|[\'"\/]+$)/', '', $url ); 90 | 91 | $ext = self::getFormate($url); 92 | list($msec,$sec) = explode ( " ", microtime () ); 93 | $seq = str_replace('0.','~',$msec); 94 | $filename = date('Ymd~His',$sec). "$seq.$ext"; 95 | 96 | $originalFile = $dir . DIRECTORY_SEPARATOR . $filename; 97 | $res = Curlex::get($url,$originalFile); 98 | if($res === true){ 99 | return $originalFile; 100 | } 101 | else{ 102 | return $res; 103 | } 104 | } 105 | 106 | 107 | /** 108 | * 图片缩放 109 | * @param $src : 源图片路径 110 | * @param $dest : 目标图片路径 111 | * @param array $size : 图片的 [宽度,高度] 112 | * @return bool 113 | */ 114 | static public function resize($src,$dest,$size = [120,160]){ 115 | 116 | if(!file_exists($src)){ 117 | return "源文件不存在: $src"; 118 | } 119 | 120 | $destDir = dirname($dest); 121 | if(!is_dir($destDir)) 122 | { 123 | mkdir($destDir,0775,true); 124 | } 125 | 126 | list($distWidth,$distHeight) = $size; 127 | 128 | list($width, $height) = getimagesize($src); 129 | $percent = 1; 130 | if($width /$height >= $distWidth/$distHeight) 131 | { 132 | 133 | if($width > $distWidth) 134 | { 135 | $percent = $distHeight/$height; 136 | } 137 | } 138 | else 139 | { 140 | if($height > $distHeight) 141 | { 142 | $percent = $distWidth/$width; 143 | } 144 | } 145 | $new_width = $width * $percent; 146 | $new_height = $height * $percent; 147 | //创建新的图片此图片的标志为$image_p 148 | $image_p = imagecreatetruecolor($new_width, $new_height); 149 | $image = imagecreatefromjpeg($src); 150 | imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height); 151 | 152 | // Output 153 | imagejpeg($image_p, $dest, 100);//quality为图片输出的质量范围从 0(最差质量,文件更小)到 100(最佳质量,文件最大)。 154 | 155 | return true; 156 | } 157 | 158 | } -------------------------------------------------------------------------------- /public/js/lazyload.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lazy Load - JavaScript plugin for lazy loading images 3 | * 4 | * Copyright (c) 2007-2017 Mika Tuupola 5 | * 6 | * Licensed under the MIT license: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * 9 | * Project home: 10 | * https://appelsiini.net/projects/lazyload 11 | * 12 | * Version: 2.0.0-beta.2 13 | * 14 | */ 15 | 16 | (function (root, factory) { 17 | if (typeof exports === "object") { 18 | module.exports = factory(root); 19 | } else if (typeof define === "function" && define.amd) { 20 | define([], factory(root)); 21 | } else { 22 | root.LazyLoad = factory(root); 23 | } 24 | }) (typeof global !== "undefined" ? global : this.window || this.global, function (root) { 25 | 26 | "use strict"; 27 | 28 | const defaults = { 29 | src: "data-src", 30 | srcset: "data-srcset", 31 | selector: ".lazyload" 32 | }; 33 | 34 | /** 35 | * Merge two or more objects. Returns a new object. 36 | * @private 37 | * @param {Boolean} deep If true, do a deep (or recursive) merge [optional] 38 | * @param {Object} objects The objects to merge together 39 | * @returns {Object} Merged values of defaults and options 40 | */ 41 | const extend = function () { 42 | 43 | let extended = {}; 44 | let deep = false; 45 | let i = 0; 46 | let length = arguments.length; 47 | 48 | /* Check if a deep merge */ 49 | if (Object.prototype.toString.call(arguments[0]) === "[object Boolean]") { 50 | deep = arguments[0]; 51 | i++; 52 | } 53 | 54 | /* Merge the object into the extended object */ 55 | let merge = function (obj) { 56 | for (let prop in obj) { 57 | if (Object.prototype.hasOwnProperty.call(obj, prop)) { 58 | /* If deep merge and property is an object, merge properties */ 59 | if (deep && Object.prototype.toString.call(obj[prop]) === "[object Object]") { 60 | extended[prop] = extend(true, extended[prop], obj[prop]); 61 | } else { 62 | extended[prop] = obj[prop]; 63 | } 64 | } 65 | } 66 | }; 67 | 68 | /* Loop through each object and conduct a merge */ 69 | for (; i < length; i++) { 70 | let obj = arguments[i]; 71 | merge(obj); 72 | } 73 | 74 | return extended; 75 | }; 76 | 77 | function LazyLoad(images, options) { 78 | this.settings = extend(defaults, options || {}); 79 | this.images = images || document.querySelectorAll(this.settings.selector); 80 | this.observer = null; 81 | this.init(); 82 | } 83 | 84 | LazyLoad.prototype = { 85 | init: function() { 86 | 87 | /* Without observers load everything and bail out early. */ 88 | if (!root.IntersectionObserver) { 89 | this.loadImages(); 90 | return; 91 | } 92 | 93 | let self = this; 94 | let observerConfig = { 95 | root: null, 96 | rootMargin: "0px", 97 | threshold: [0] 98 | }; 99 | 100 | this.observer = new IntersectionObserver(function(entries) { 101 | entries.forEach(function (entry) { 102 | if (entry.intersectionRatio > 0) { 103 | self.observer.unobserve(entry.target); 104 | let src = entry.target.getAttribute(self.settings.src); 105 | let srcset = entry.target.getAttribute(self.settings.srcset); 106 | if ("img" === entry.target.tagName.toLowerCase()) { 107 | if (src) { 108 | entry.target.src = src; 109 | } 110 | if (srcset) { 111 | entry.target.srcset = srcset; 112 | } 113 | } else { 114 | entry.target.style.backgroundImage = "url(" + src + ")"; 115 | } 116 | } 117 | }); 118 | }, observerConfig); 119 | 120 | this.images.forEach(function (image) { 121 | self.observer.observe(image); 122 | }); 123 | }, 124 | 125 | loadAndDestroy: function () { 126 | if (!this.settings) { return; } 127 | this.loadImages(); 128 | this.destroy(); 129 | }, 130 | 131 | loadImages: function () { 132 | if (!this.settings) { return; } 133 | 134 | let self = this; 135 | this.images.forEach(function (image) { 136 | let src = image.getAttribute(self.settings.src); 137 | let srcset = image.getAttribute(self.settings.srcset); 138 | if ("img" === image.tagName.toLowerCase()) { 139 | if (src) { 140 | image.src = src; 141 | } 142 | if (srcset) { 143 | image.srcset = srcset; 144 | } 145 | } else { 146 | image.style.backgroundImage = "url(" + src + ")"; 147 | } 148 | }); 149 | }, 150 | 151 | destroy: function () { 152 | if (!this.settings) { return; } 153 | this.observer.disconnect(); 154 | this.settings = null; 155 | } 156 | }; 157 | 158 | root.lazyload = function(images, options) { 159 | return new LazyLoad(images, options); 160 | }; 161 | 162 | if (root.jQuery) { 163 | const $ = root.jQuery; 164 | $.fn.lazyload = function (options) { 165 | options = options || {}; 166 | options.attribute = options.attribute || "data-src"; 167 | new LazyLoad($.makeArray(this), options); 168 | return this; 169 | }; 170 | } 171 | 172 | return LazyLoad; 173 | }); 174 | -------------------------------------------------------------------------------- /public/wall2/CSS3DRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs 3 | * @author mrdoob / http://mrdoob.com/ 4 | */ 5 | 6 | THREE.CSS3DObject = function ( element ) { 7 | 8 | THREE.Object3D.call( this ); 9 | 10 | this.element = element; 11 | this.element.style.position = 'absolute'; 12 | 13 | this.addEventListener( 'removed', function ( event ) { 14 | 15 | if ( this.element.parentNode !== null ) { 16 | 17 | this.element.parentNode.removeChild( this.element ); 18 | 19 | for ( var i = 0, l = this.children.length; i < l; i ++ ) { 20 | 21 | this.children[ i ].dispatchEvent( event ); 22 | 23 | } 24 | 25 | } 26 | 27 | } ); 28 | 29 | }; 30 | 31 | THREE.CSS3DObject.prototype = Object.create( THREE.Object3D.prototype ); 32 | 33 | THREE.CSS3DSprite = function ( element ) { 34 | 35 | THREE.CSS3DObject.call( this, element ); 36 | 37 | }; 38 | 39 | THREE.CSS3DSprite.prototype = Object.create( THREE.CSS3DObject.prototype ); 40 | 41 | // 42 | 43 | THREE.CSS3DRenderer = function () { 44 | 45 | console.log( 'THREE.CSS3DRenderer', THREE.REVISION ); 46 | 47 | var _width, _height; 48 | var _widthHalf, _heightHalf; 49 | 50 | var matrix = new THREE.Matrix4(); 51 | 52 | var domElement = document.createElement( 'div' ); 53 | domElement.style.overflow = 'hidden'; 54 | 55 | domElement.style.WebkitTransformStyle = 'preserve-3d'; 56 | domElement.style.MozTransformStyle = 'preserve-3d'; 57 | domElement.style.oTransformStyle = 'preserve-3d'; 58 | domElement.style.transformStyle = 'preserve-3d'; 59 | 60 | this.domElement = domElement; 61 | 62 | var cameraElement = document.createElement( 'div' ); 63 | 64 | cameraElement.style.WebkitTransformStyle = 'preserve-3d'; 65 | cameraElement.style.MozTransformStyle = 'preserve-3d'; 66 | cameraElement.style.oTransformStyle = 'preserve-3d'; 67 | cameraElement.style.transformStyle = 'preserve-3d'; 68 | 69 | domElement.appendChild( cameraElement ); 70 | 71 | this.setClearColor = function () { 72 | 73 | }; 74 | 75 | this.setSize = function ( width, height ) { 76 | 77 | _width = width; 78 | _height = height; 79 | 80 | _widthHalf = _width / 2; 81 | _heightHalf = _height / 2; 82 | 83 | domElement.style.width = width + 'px'; 84 | domElement.style.height = height + 'px'; 85 | 86 | cameraElement.style.width = width + 'px'; 87 | cameraElement.style.height = height + 'px'; 88 | 89 | }; 90 | 91 | var epsilon = function ( value ) { 92 | 93 | return Math.abs( value ) < 0.000001 ? 0 : value; 94 | 95 | }; 96 | 97 | var getCameraCSSMatrix = function ( matrix ) { 98 | 99 | var elements = matrix.elements; 100 | 101 | return 'matrix3d(' + 102 | epsilon( elements[ 0 ] ) + ',' + 103 | epsilon( - elements[ 1 ] ) + ',' + 104 | epsilon( elements[ 2 ] ) + ',' + 105 | epsilon( elements[ 3 ] ) + ',' + 106 | epsilon( elements[ 4 ] ) + ',' + 107 | epsilon( - elements[ 5 ] ) + ',' + 108 | epsilon( elements[ 6 ] ) + ',' + 109 | epsilon( elements[ 7 ] ) + ',' + 110 | epsilon( elements[ 8 ] ) + ',' + 111 | epsilon( - elements[ 9 ] ) + ',' + 112 | epsilon( elements[ 10 ] ) + ',' + 113 | epsilon( elements[ 11 ] ) + ',' + 114 | epsilon( elements[ 12 ] ) + ',' + 115 | epsilon( - elements[ 13 ] ) + ',' + 116 | epsilon( elements[ 14 ] ) + ',' + 117 | epsilon( elements[ 15 ] ) + 118 | ')'; 119 | 120 | }; 121 | 122 | var getObjectCSSMatrix = function ( matrix ) { 123 | 124 | var elements = matrix.elements; 125 | 126 | return 'translate3d(-50%,-50%,0) matrix3d(' + 127 | epsilon( elements[ 0 ] ) + ',' + 128 | epsilon( elements[ 1 ] ) + ',' + 129 | epsilon( elements[ 2 ] ) + ',' + 130 | epsilon( elements[ 3 ] ) + ',' + 131 | epsilon( - elements[ 4 ] ) + ',' + 132 | epsilon( - elements[ 5 ] ) + ',' + 133 | epsilon( - elements[ 6 ] ) + ',' + 134 | epsilon( - elements[ 7 ] ) + ',' + 135 | epsilon( elements[ 8 ] ) + ',' + 136 | epsilon( elements[ 9 ] ) + ',' + 137 | epsilon( elements[ 10 ] ) + ',' + 138 | epsilon( elements[ 11 ] ) + ',' + 139 | epsilon( elements[ 12 ] ) + ',' + 140 | epsilon( elements[ 13 ] ) + ',' + 141 | epsilon( elements[ 14 ] ) + ',' + 142 | epsilon( elements[ 15 ] ) + 143 | ')'; 144 | 145 | }; 146 | 147 | var renderObject = function ( object, camera ) { 148 | 149 | if ( object instanceof THREE.CSS3DObject ) { 150 | 151 | var style; 152 | 153 | if ( object instanceof THREE.CSS3DSprite ) { 154 | 155 | // http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/ 156 | 157 | matrix.copy( camera.matrixWorldInverse ); 158 | matrix.transpose(); 159 | matrix.copyPosition( object.matrixWorld ); 160 | matrix.scale( object.scale ); 161 | 162 | matrix.elements[ 3 ] = 0; 163 | matrix.elements[ 7 ] = 0; 164 | matrix.elements[ 11 ] = 0; 165 | matrix.elements[ 15 ] = 1; 166 | 167 | style = getObjectCSSMatrix( matrix ); 168 | 169 | } else { 170 | 171 | style = getObjectCSSMatrix( object.matrixWorld ); 172 | 173 | } 174 | 175 | var element = object.element; 176 | 177 | element.style.WebkitTransform = style; 178 | element.style.MozTransform = style; 179 | element.style.oTransform = style; 180 | element.style.transform = style; 181 | 182 | if ( element.parentNode !== cameraElement ) { 183 | 184 | cameraElement.appendChild( element ); 185 | 186 | } 187 | 188 | } 189 | 190 | for ( var i = 0, l = object.children.length; i < l; i ++ ) { 191 | 192 | renderObject( object.children[ i ], camera ); 193 | 194 | } 195 | 196 | }; 197 | 198 | this.render = function ( scene, camera ) { 199 | 200 | var fov = 0.5 / Math.tan( THREE.Math.degToRad( camera.fov * 0.5 ) ) * _height; 201 | 202 | domElement.style.WebkitPerspective = fov + "px"; 203 | domElement.style.MozPerspective = fov + "px"; 204 | domElement.style.oPerspective = fov + "px"; 205 | domElement.style.perspective = fov + "px"; 206 | 207 | scene.updateMatrixWorld(); 208 | 209 | if ( camera.parent === undefined ) camera.updateMatrixWorld(); 210 | 211 | camera.matrixWorldInverse.getInverse( camera.matrixWorld ); 212 | 213 | var style = "translate3d(0,0," + fov + "px)" + getCameraCSSMatrix( camera.matrixWorldInverse ) + 214 | " translate3d(" + _widthHalf + "px," + _heightHalf + "px, 0)"; 215 | 216 | cameraElement.style.WebkitTransform = style; 217 | cameraElement.style.MozTransform = style; 218 | cameraElement.style.oTransform = style; 219 | cameraElement.style.transform = style; 220 | 221 | renderObject( scene, camera ); 222 | 223 | }; 224 | }; -------------------------------------------------------------------------------- /src/controllers/Weixin/IndexController.php: -------------------------------------------------------------------------------- 1 | container = $container; 30 | $this->cfg = $container->get("settings")['weixin']; 31 | $this->db = $container->get("db"); 32 | $this->resource = $container->get("settings")['resource']; 33 | } 34 | 35 | public function vaild($req,$res,$args){ 36 | $app = Factory::officialAccount($this->cfg); 37 | $response = $app->server->serve(); 38 | // 将响应输出 39 | return $response->send(); 40 | } 41 | 42 | public function index($req,$res,$args){ 43 | // 使用配置来初始化一个公众号应用实例。 44 | $app = Factory::officialAccount($this->cfg); 45 | 46 | $app->server->push(function ($message) { 47 | $user = $message['FromUserName']; 48 | $mUser = new User($this->db); 49 | $uid = $mUser->getUidByOpenid($user); 50 | $userCenter = "http://" . $_SERVER['HTTP_HOST'] . "/wall" .DIRECTORY_SEPARATOR .$uid; 51 | 52 | $mMsg = new Message($this->db); 53 | 54 | 55 | switch ( $type = $message['MsgType']) { 56 | case 'event': 57 | if($message['Event'] == "subscribe"){ 58 | return '欢迎来到拍拍客,请直接发送图片,即可看到自己的照片墙!输入:"我的"或者"mine"可以看到自己的照片墙地址'; 59 | } 60 | elseif($message['Event'] == "unsubscribe"){ 61 | return '欢迎再次光临,祝您生活愉快!'; 62 | } 63 | 64 | return '收到事件消息'.$message['Event']; 65 | break; 66 | case 'text': 67 | if ($message['Content'] == "mine" || $message['Content'] == "我的"){ 68 | return $this->response($userCenter, "" ,"点击进入您的照片墙.或者在浏览器打开: $userCenter", "您的拍拍客地址"); 69 | } 70 | 71 | return '收到文字消息:' . $message['Content']; 72 | break; 73 | case 'image': 74 | $url = $message['PicUrl']; 75 | 76 | $this->container->logger->info("收到图片:$url"); 77 | 78 | $imageName = $this->image($url, $uid); 79 | if($imageName === false){ 80 | return $this->response($url, $url ,$user, "图片操作失败"); 81 | } 82 | $this->container->logger->info("转码成功,$imageName"); 83 | 84 | if($qnUrl = $this->upQiniu($url,"pper_uid$uid"."_".$imageName)){ // 上传到七牛云 85 | $orgImage = $qnUrl; 86 | $this->container->logger->info("七牛云上传成功,$orgImage"); 87 | //删除本地图片 88 | unlink($this->resource['path'] . DIRECTORY_SEPARATOR . "$uid/original/$imageName"); // 删除本地原图 89 | $this->container->logger->info("删除本地成功,".$this->resource['path'] . DIRECTORY_SEPARATOR . "$uid/original/$imageName"); 90 | } 91 | else{ 92 | $orgImage = $this->resource['url'] . DIRECTORY_SEPARATOR . "$uid/original/$imageName"; 93 | $this->container->logger->info("上传失败,$orgImage"); 94 | } 95 | 96 | $resize = $this->resource['url'] . DIRECTORY_SEPARATOR . "$uid/resize/$imageName"; 97 | $msg = $mMsg->saveMessage($uid,$type,$resize,$orgImage); 98 | $this->container->logger->info("数据库更新失败,$msg"); 99 | 100 | return $this->response($userCenter, $resize,'图片已经上墙,点击或者刷新可以查看最新的照片墙','上墙通知'); 101 | 102 | break; 103 | case 'voice': 104 | return '收到语音消息'; 105 | break; 106 | case 'video': 107 | return '收到视频消息'; 108 | break; 109 | case 'location': 110 | return '收到坐标消息'; 111 | break; 112 | case 'link': 113 | return '收到链接消息'; 114 | break; 115 | case 'file': 116 | return '收到文件消息'; 117 | // ... 其它消息 118 | default: 119 | return '收到其它消息'; 120 | break; 121 | } 122 | 123 | // ... 124 | }); 125 | $response = $app->server->serve(); 126 | 127 | // 将响应输出 128 | return $response->send(); 129 | } 130 | 131 | 132 | /** 133 | * 测试代码使用 134 | * @param $request 135 | * @param $response 136 | * @param $args 137 | */ 138 | public function ttt($request, $response, $args){ 139 | set_error_handler(function ($errno, $errstr) { 140 | // do something 141 | echo "\r\n~~~~~找到错误 :-D~~~~~\r\n"; 142 | echo "错误信息:($errno)$errstr \r\n"; 143 | debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); 144 | echo "\r\n"; 145 | }, E_WARNING); 146 | 147 | $url = 'http://mmbiz.qpic.cn/mmbiz_jpg/EuJU30O3lFKeOmC254TagSVpKL1h4ibSA8lLQXeia4TrVo6F3s7mPQxBvJia9XNeMbmmWtVBkeib7ibicJU34mlMY6hw/0'; 148 | $openId = 'os_G_jqeNPMfWLA-A2KIKodfm3SY'; 149 | 150 | $mUser = new User($this->db); 151 | 152 | $uid = $mUser->getUid($openId); 153 | 154 | $userCenter = "http://".$_SERVER['HTTP_HOST'] . "/users/$uid"; 155 | // return $this->response($url,$userCenter); 156 | 157 | //$this->image($url,$uid); 158 | 159 | $imageName = $this->image($url, $uid); 160 | if($imageName === false){ 161 | return $this->response($url, $url ,$openId, "图片操作失败"); 162 | } 163 | 164 | if($qnUrl = $this->upQiniu($url,"pper_uid$uid"."_".$imageName)){ // 上传到七牛云 165 | unlink($this->resource['path'] . DIRECTORY_SEPARATOR . "$uid/original/$imageName"); // 删除本地原图 166 | $orgImage = $qnUrl; 167 | 168 | } 169 | else{ 170 | $orgImage = $this->resource['url'] . DIRECTORY_SEPARATOR . "$uid/original/$imageName"; 171 | } 172 | 173 | restore_error_handler(); 174 | //echo $orgImage; 175 | //*/ 176 | } 177 | 178 | 179 | 180 | private function upQiniu($src,$name){ 181 | $cfg = $this->container->get("settings")['qiniu']; 182 | if(!$cfg['enable']){ 183 | return false; 184 | } 185 | $qn = new Qiniu($cfg); 186 | $url = $qn->upload($src,$name); 187 | return $url; 188 | } 189 | 190 | 191 | private function image($url,$uid){ 192 | $dir = $this->container->get("settings")['resource']; 193 | $originalDir = $dir['path'] . DIRECTORY_SEPARATOR . $uid . DIRECTORY_SEPARATOR . "original"; 194 | $resizeDir = $dir['path'] . DIRECTORY_SEPARATOR . $uid . DIRECTORY_SEPARATOR . "resize"; 195 | $img = Photo::download($url,$originalDir); 196 | $imageName = basename($img); 197 | if(file_exists($img)){ 198 | $resieFile = $resizeDir.DIRECTORY_SEPARATOR.$imageName; 199 | if(Photo::resize($img,$resizeDir.DIRECTORY_SEPARATOR.$imageName) !== true){ 200 | $this->container->logger->error("生成缩略失败:$resieFile"); 201 | return false; 202 | } 203 | else { 204 | return $imageName; 205 | } 206 | } 207 | else{ 208 | $this->container->logger->error("图片下载失败:$url\r\n目标位置: $img"); 209 | return false; 210 | } 211 | } 212 | 213 | private function response($url,$image,$desc,$title='拍拍客') 214 | { 215 | $items = [ 216 | new NewsItem([ 217 | 'title' => $title, 218 | 'description' => $desc, 219 | 'url' => $url, 220 | 'image' => $image, 221 | // ... 222 | ]), 223 | ]; 224 | return $news = new News($items); 225 | } 226 | 227 | } -------------------------------------------------------------------------------- /public/js/CSS3DRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs 3 | * @author mrdoob / http://mrdoob.com/ 4 | * @author yomotsu / https://yomotsu.net/ 5 | */ 6 | 7 | THREE.CSS3DObject = function ( element ) { 8 | 9 | THREE.Object3D.call( this ); 10 | 11 | this.element = element; 12 | this.element.style.position = 'absolute'; 13 | 14 | this.addEventListener( 'removed', function () { 15 | 16 | if ( this.element.parentNode !== null ) { 17 | 18 | this.element.parentNode.removeChild( this.element ); 19 | 20 | } 21 | 22 | } ); 23 | 24 | }; 25 | 26 | THREE.CSS3DObject.prototype = Object.create( THREE.Object3D.prototype ); 27 | THREE.CSS3DObject.prototype.constructor = THREE.CSS3DObject; 28 | 29 | THREE.CSS3DSprite = function ( element ) { 30 | 31 | THREE.CSS3DObject.call( this, element ); 32 | 33 | }; 34 | 35 | THREE.CSS3DSprite.prototype = Object.create( THREE.CSS3DObject.prototype ); 36 | THREE.CSS3DSprite.prototype.constructor = THREE.CSS3DSprite; 37 | 38 | // 39 | 40 | THREE.CSS3DRenderer = function () { 41 | 42 | console.log( 'THREE.CSS3DRenderer', THREE.REVISION ); 43 | 44 | var _width, _height; 45 | var _widthHalf, _heightHalf; 46 | 47 | var matrix = new THREE.Matrix4(); 48 | 49 | var cache = { 50 | camera: { fov: 0, style: '' }, 51 | objects: new WeakMap() 52 | }; 53 | 54 | var domElement = document.createElement( 'div' ); 55 | domElement.style.overflow = 'hidden'; 56 | 57 | this.domElement = domElement; 58 | 59 | var cameraElement = document.createElement( 'div' ); 60 | 61 | cameraElement.style.WebkitTransformStyle = 'preserve-3d'; 62 | cameraElement.style.transformStyle = 'preserve-3d'; 63 | 64 | domElement.appendChild( cameraElement ); 65 | 66 | var isIE = /Trident/i.test( navigator.userAgent ); 67 | 68 | this.getSize = function () { 69 | 70 | return { 71 | width: _width, 72 | height: _height 73 | }; 74 | 75 | }; 76 | 77 | this.setSize = function ( width, height ) { 78 | 79 | _width = width; 80 | _height = height; 81 | _widthHalf = _width / 2; 82 | _heightHalf = _height / 2; 83 | 84 | domElement.style.width = width + 'px'; 85 | domElement.style.height = height + 'px'; 86 | 87 | cameraElement.style.width = width + 'px'; 88 | cameraElement.style.height = height + 'px'; 89 | 90 | }; 91 | 92 | function epsilon( value ) { 93 | 94 | return Math.abs( value ) < 1e-10 ? 0 : value; 95 | 96 | } 97 | 98 | function getCameraCSSMatrix( matrix ) { 99 | 100 | var elements = matrix.elements; 101 | 102 | return 'matrix3d(' + 103 | epsilon( elements[ 0 ] ) + ',' + 104 | epsilon( - elements[ 1 ] ) + ',' + 105 | epsilon( elements[ 2 ] ) + ',' + 106 | epsilon( elements[ 3 ] ) + ',' + 107 | epsilon( elements[ 4 ] ) + ',' + 108 | epsilon( - elements[ 5 ] ) + ',' + 109 | epsilon( elements[ 6 ] ) + ',' + 110 | epsilon( elements[ 7 ] ) + ',' + 111 | epsilon( elements[ 8 ] ) + ',' + 112 | epsilon( - elements[ 9 ] ) + ',' + 113 | epsilon( elements[ 10 ] ) + ',' + 114 | epsilon( elements[ 11 ] ) + ',' + 115 | epsilon( elements[ 12 ] ) + ',' + 116 | epsilon( - elements[ 13 ] ) + ',' + 117 | epsilon( elements[ 14 ] ) + ',' + 118 | epsilon( elements[ 15 ] ) + 119 | ')'; 120 | 121 | } 122 | 123 | function getObjectCSSMatrix( matrix, cameraCSSMatrix ) { 124 | 125 | var elements = matrix.elements; 126 | var matrix3d = 'matrix3d(' + 127 | epsilon( elements[ 0 ] ) + ',' + 128 | epsilon( elements[ 1 ] ) + ',' + 129 | epsilon( elements[ 2 ] ) + ',' + 130 | epsilon( elements[ 3 ] ) + ',' + 131 | epsilon( - elements[ 4 ] ) + ',' + 132 | epsilon( - elements[ 5 ] ) + ',' + 133 | epsilon( - elements[ 6 ] ) + ',' + 134 | epsilon( - elements[ 7 ] ) + ',' + 135 | epsilon( elements[ 8 ] ) + ',' + 136 | epsilon( elements[ 9 ] ) + ',' + 137 | epsilon( elements[ 10 ] ) + ',' + 138 | epsilon( elements[ 11 ] ) + ',' + 139 | epsilon( elements[ 12 ] ) + ',' + 140 | epsilon( elements[ 13 ] ) + ',' + 141 | epsilon( elements[ 14 ] ) + ',' + 142 | epsilon( elements[ 15 ] ) + 143 | ')'; 144 | 145 | if ( isIE ) { 146 | 147 | return 'translate(-50%,-50%)' + 148 | 'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)' + 149 | cameraCSSMatrix + 150 | matrix3d; 151 | 152 | } 153 | 154 | return 'translate(-50%,-50%)' + matrix3d; 155 | 156 | } 157 | 158 | function renderObject( object, camera, cameraCSSMatrix ) { 159 | 160 | if ( object instanceof THREE.CSS3DObject ) { 161 | 162 | var style; 163 | 164 | if ( object instanceof THREE.CSS3DSprite ) { 165 | 166 | // http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/ 167 | 168 | matrix.copy( camera.matrixWorldInverse ); 169 | matrix.transpose(); 170 | matrix.copyPosition( object.matrixWorld ); 171 | matrix.scale( object.scale ); 172 | 173 | matrix.elements[ 3 ] = 0; 174 | matrix.elements[ 7 ] = 0; 175 | matrix.elements[ 11 ] = 0; 176 | matrix.elements[ 15 ] = 1; 177 | 178 | style = getObjectCSSMatrix( matrix, cameraCSSMatrix ); 179 | 180 | } else { 181 | 182 | style = getObjectCSSMatrix( object.matrixWorld, cameraCSSMatrix ); 183 | 184 | } 185 | 186 | var element = object.element; 187 | var cachedObject = cache.objects.get( object ); 188 | 189 | if ( cachedObject === undefined || cachedObject.style !== style ) { 190 | 191 | element.style.WebkitTransform = style; 192 | element.style.transform = style; 193 | 194 | var objectData = { style: style }; 195 | 196 | if ( isIE ) { 197 | 198 | objectData.distanceToCameraSquared = getDistanceToSquared( camera, object ); 199 | 200 | } 201 | 202 | cache.objects.set( object, objectData ); 203 | 204 | } 205 | 206 | if ( element.parentNode !== cameraElement ) { 207 | 208 | cameraElement.appendChild( element ); 209 | 210 | } 211 | 212 | } 213 | 214 | for ( var i = 0, l = object.children.length; i < l; i ++ ) { 215 | 216 | renderObject( object.children[ i ], camera, cameraCSSMatrix ); 217 | 218 | } 219 | 220 | } 221 | 222 | var getDistanceToSquared = function () { 223 | 224 | var a = new THREE.Vector3(); 225 | var b = new THREE.Vector3(); 226 | 227 | return function ( object1, object2 ) { 228 | 229 | a.setFromMatrixPosition( object1.matrixWorld ); 230 | b.setFromMatrixPosition( object2.matrixWorld ); 231 | 232 | return a.distanceToSquared( b ); 233 | 234 | }; 235 | 236 | }(); 237 | 238 | function filterAndFlatten( scene ) { 239 | 240 | var result = []; 241 | 242 | scene.traverse( function ( object ) { 243 | 244 | if ( object instanceof THREE.CSS3DObject ) result.push( object ); 245 | 246 | } ); 247 | 248 | return result; 249 | 250 | } 251 | 252 | function zOrder( scene ) { 253 | 254 | var sorted = filterAndFlatten( scene ).sort( function ( a, b ) { 255 | 256 | var distanceA = cache.objects.get( a ).distanceToCameraSquared; 257 | var distanceB = cache.objects.get( b ).distanceToCameraSquared; 258 | 259 | return distanceA - distanceB; 260 | 261 | } ); 262 | 263 | var zMax = sorted.length; 264 | 265 | for ( var i = 0, l = sorted.length; i < l; i ++ ) { 266 | 267 | sorted[ i ].element.style.zIndex = zMax - i; 268 | 269 | } 270 | 271 | } 272 | 273 | this.render = function ( scene, camera ) { 274 | 275 | var fov = camera.projectionMatrix.elements[ 5 ] * _heightHalf; 276 | 277 | if ( cache.camera.fov !== fov ) { 278 | 279 | if ( camera.isPerspectiveCamera ) { 280 | 281 | domElement.style.WebkitPerspective = fov + 'px'; 282 | domElement.style.perspective = fov + 'px'; 283 | 284 | } 285 | 286 | cache.camera.fov = fov; 287 | 288 | } 289 | 290 | scene.updateMatrixWorld(); 291 | 292 | if ( camera.parent === null ) camera.updateMatrixWorld(); 293 | 294 | if ( camera.isOrthographicCamera ) { 295 | 296 | var tx = - ( camera.right + camera.left ) / 2; 297 | var ty = ( camera.top + camera.bottom ) / 2; 298 | 299 | } 300 | 301 | var cameraCSSMatrix = camera.isOrthographicCamera ? 302 | 'scale(' + fov + ')' + 'translate(' + epsilon( tx ) + 'px,' + epsilon( ty ) + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse ) : 303 | 'translateZ(' + fov + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse ); 304 | 305 | var style = cameraCSSMatrix + 306 | 'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)'; 307 | 308 | if ( cache.camera.style !== style && ! isIE ) { 309 | 310 | cameraElement.style.WebkitTransform = style; 311 | cameraElement.style.transform = style; 312 | 313 | cache.camera.style = style; 314 | 315 | } 316 | 317 | renderObject( scene, camera, cameraCSSMatrix ); 318 | 319 | if ( isIE ) { 320 | 321 | // IE10 and 11 does not support 'preserve-3d'. 322 | // Thus, z-order in 3D will not work. 323 | // We have to calc z-order manually and set CSS z-index for IE. 324 | // FYI: z-index can't handle object intersection 325 | zOrder( scene ); 326 | 327 | } 328 | 329 | }; 330 | 331 | }; 332 | -------------------------------------------------------------------------------- /public/js/lightbox.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lightbox v2.10.0 3 | * by Lokesh Dhakar 4 | * 5 | * More info: 6 | * http://lokeshdhakar.com/projects/lightbox2/ 7 | * 8 | * Copyright 2007, 2018 Lokesh Dhakar 9 | * Released under the MIT license 10 | * https://github.com/lokesh/lightbox2/blob/master/LICENSE 11 | * 12 | * @preserve 13 | */ 14 | !function(a,b){"function"==typeof define&&define.amd?define(["jquery"],b):"object"==typeof exports?module.exports=b(require("jquery")):a.lightbox=b(a.jQuery)}(this,function(a){function b(b){this.album=[],this.currentImageIndex=void 0,this.init(),this.options=a.extend({},this.constructor.defaults),this.option(b)}return b.defaults={albumLabel:"Image %1 of %2",alwaysShowNavOnTouchDevices:!1,fadeDuration:600,fitImagesInViewport:!0,imageFadeDuration:600,positionFromTop:50,resizeDuration:700,showImageNumberLabel:!0,wrapAround:!1,disableScrolling:!1,sanitizeTitle:!1},b.prototype.option=function(b){a.extend(this.options,b)},b.prototype.imageCountLabel=function(a,b){return this.options.albumLabel.replace(/%1/g,a).replace(/%2/g,b)},b.prototype.init=function(){var b=this;a(document).ready(function(){b.enable(),b.build()})},b.prototype.enable=function(){var b=this;a("body").on("click","a[rel^=lightbox], area[rel^=lightbox], a[data-lightbox], area[data-lightbox]",function(c){return b.start(a(c.currentTarget)),!1})},b.prototype.build=function(){if(!(a("#lightbox").length>0)){var b=this;a('
').appendTo(a("body")),this.$lightbox=a("#lightbox"),this.$overlay=a("#lightboxOverlay"),this.$outerContainer=this.$lightbox.find(".lb-outerContainer"),this.$container=this.$lightbox.find(".lb-container"),this.$image=this.$lightbox.find(".lb-image"),this.$nav=this.$lightbox.find(".lb-nav"),this.containerPadding={top:parseInt(this.$container.css("padding-top"),10),right:parseInt(this.$container.css("padding-right"),10),bottom:parseInt(this.$container.css("padding-bottom"),10),left:parseInt(this.$container.css("padding-left"),10)},this.imageBorderWidth={top:parseInt(this.$image.css("border-top-width"),10),right:parseInt(this.$image.css("border-right-width"),10),bottom:parseInt(this.$image.css("border-bottom-width"),10),left:parseInt(this.$image.css("border-left-width"),10)},this.$overlay.hide().on("click",function(){return b.end(),!1}),this.$lightbox.hide().on("click",function(c){return"lightbox"===a(c.target).attr("id")&&b.end(),!1}),this.$outerContainer.on("click",function(c){return"lightbox"===a(c.target).attr("id")&&b.end(),!1}),this.$lightbox.find(".lb-prev").on("click",function(){return 0===b.currentImageIndex?b.changeImage(b.album.length-1):b.changeImage(b.currentImageIndex-1),!1}),this.$lightbox.find(".lb-next").on("click",function(){return b.currentImageIndex===b.album.length-1?b.changeImage(0):b.changeImage(b.currentImageIndex+1),!1}),this.$nav.on("mousedown",function(a){3===a.which&&(b.$nav.css("pointer-events","none"),b.$lightbox.one("contextmenu",function(){setTimeout(function(){this.$nav.css("pointer-events","auto")}.bind(b),0)}))}),this.$lightbox.find(".lb-loader, .lb-close").on("click",function(){return b.end(),!1})}},b.prototype.start=function(b){function c(a){d.album.push({alt:a.attr("data-alt"),link:a.attr("href"),title:a.attr("data-title")||a.attr("title")})}var d=this,e=a(window);e.on("resize",a.proxy(this.sizeOverlay,this)),a("select, object, embed").css({visibility:"hidden"}),this.sizeOverlay(),this.album=[];var f,g=0,h=b.attr("data-lightbox");if(h){f=a(b.prop("tagName")+'[data-lightbox="'+h+'"]');for(var i=0;ii||e.height>h)&&(e.width/i>e.height/h?(g=i,f=parseInt(e.height/(e.width/g),10),d.width(g),d.height(f)):(f=h,g=parseInt(e.width/(e.height/f),10),d.width(g),d.height(f)))),c.sizeContainer(d.width(),d.height())},e.src=this.album[b].link,this.currentImageIndex=b},b.prototype.sizeOverlay=function(){this.$overlay.width(a(document).width()).height(a(document).height())},b.prototype.sizeContainer=function(a,b){function c(){d.$lightbox.find(".lb-dataContainer").width(g),d.$lightbox.find(".lb-prevLink").height(h),d.$lightbox.find(".lb-nextLink").height(h),d.showImage()}var d=this,e=this.$outerContainer.outerWidth(),f=this.$outerContainer.outerHeight(),g=a+this.containerPadding.left+this.containerPadding.right+this.imageBorderWidth.left+this.imageBorderWidth.right,h=b+this.containerPadding.top+this.containerPadding.bottom+this.imageBorderWidth.top+this.imageBorderWidth.bottom;e!==g||f!==h?this.$outerContainer.animate({width:g,height:h},this.options.resizeDuration,"swing",function(){c()}):c()},b.prototype.showImage=function(){this.$lightbox.find(".lb-loader").stop(!0).hide(),this.$lightbox.find(".lb-image").fadeIn(this.options.imageFadeDuration),this.updateNav(),this.updateDetails(),this.preloadNeighboringImages(),this.enableKeyboardNav()},b.prototype.updateNav=function(){var a=!1;try{document.createEvent("TouchEvent"),a=!!this.options.alwaysShowNavOnTouchDevices}catch(a){}this.$lightbox.find(".lb-nav").show(),this.album.length>1&&(this.options.wrapAround?(a&&this.$lightbox.find(".lb-prev, .lb-next").css("opacity","1"),this.$lightbox.find(".lb-prev, .lb-next").show()):(this.currentImageIndex>0&&(this.$lightbox.find(".lb-prev").show(),a&&this.$lightbox.find(".lb-prev").css("opacity","1")),this.currentImageIndex1&&this.options.showImageNumberLabel){var d=this.imageCountLabel(this.currentImageIndex+1,this.album.length);this.$lightbox.find(".lb-number").text(d).fadeIn("fast")}else this.$lightbox.find(".lb-number").hide();this.$outerContainer.removeClass("animating"),this.$lightbox.find(".lb-dataContainer").fadeIn(this.options.resizeDuration,function(){return b.sizeOverlay()})},b.prototype.preloadNeighboringImages=function(){if(this.album.length>this.currentImageIndex+1){(new Image).src=this.album[this.currentImageIndex+1].link}if(this.currentImageIndex>0){(new Image).src=this.album[this.currentImageIndex-1].link}},b.prototype.enableKeyboardNav=function(){a(document).on("keyup.keyboard",a.proxy(this.keyboardAction,this))},b.prototype.disableKeyboardNav=function(){a(document).off(".keyboard")},b.prototype.keyboardAction=function(a){var b=a.keyCode,c=String.fromCharCode(b).toLowerCase();27===b||c.match(/x|o|c/)?this.end():"p"===c||37===b?0!==this.currentImageIndex?this.changeImage(this.currentImageIndex-1):this.options.wrapAround&&this.album.length>1&&this.changeImage(this.album.length-1):"n"!==c&&39!==b||(this.currentImageIndex!==this.album.length-1?this.changeImage(this.currentImageIndex+1):this.options.wrapAround&&this.album.length>1&&this.changeImage(0))},b.prototype.end=function(){this.disableKeyboardNav(),a(window).off("resize",this.sizeOverlay),this.$lightbox.fadeOut(this.options.fadeDuration),this.$overlay.fadeOut(this.options.fadeDuration),a("select, object, embed").css({visibility:"visible"}),this.options.disableScrolling&&a("html").removeClass("lb-disable-scrolling")},new b}); 15 | //# sourceMappingURL=lightbox.min.map -------------------------------------------------------------------------------- /public/js/wall.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by liuligang on 2019/1/2. 3 | */ 4 | 5 | //建议值[16,9] 6 | var table = []; 7 | var maxCol = 16,maxRow=9; 8 | 9 | var camera, scene, renderer; 10 | var controls; 11 | var radius = 100, theta = 0; 12 | 13 | var objects = []; 14 | var targets = { table: [], sphere: [], helix: [], grid: [] }; 15 | 16 | 17 | function initImg() { 18 | table.forEach(function(val,index){ 19 | 20 | var element = document.createElement( 'div' ); 21 | element.className = 'element'; 22 | element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')'; 23 | 24 | var a = document.createElement('a'); 25 | a.href = val.resource; 26 | a.setAttribute('data-lightbox','roadtrip'); 27 | a.setAttribute('title',a.href.substr(a.href.lastIndexOf('/')+1)); 28 | 29 | var image=new Image(); 30 | image.src=val.src; 31 | image.onload = function() { 32 | //scene.add( object ); 33 | }; 34 | //a.appendChild(image) 35 | 36 | element.appendChild( image ); 37 | // element.setAttribute('style',"background-image: url(" + val.src + ")"); 38 | a.appendChild(element); 39 | 40 | //var object = new THREE.CSS3DObject( element ); 41 | var object = new THREE.CSS3DObject( a ); 42 | object.position.x = Math.random() * 4000 - 2000; 43 | object.position.y = Math.random() * 4000 - 2000; 44 | object.position.z = Math.random() * 4000 - 2000; 45 | scene.add( object ); 46 | 47 | objects.push( object ); 48 | 49 | }); 50 | } 51 | 52 | 53 | function init() { 54 | 55 | camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 ); 56 | camera.position.z = 3000; 57 | 58 | scene = new THREE.Scene(); 59 | 60 | var col = 1, row = 1; 61 | 62 | 63 | initImg(); 64 | 65 | 66 | 67 | //table 68 | 69 | for ( var i = 0, l = objects.length; i < l; i ++ ) { 70 | var object = new THREE.Object3D(); 71 | object.position.x = (col++ * 181 ) - 1330; 72 | object.position.y = - (row * 120 ) + 990; 73 | if(col > maxCol){ 74 | col = 1; 75 | row ++; 76 | } 77 | targets.table.push( object ); 78 | } 79 | 80 | 81 | 82 | // sphere 83 | 84 | var vector = new THREE.Vector3(); 85 | 86 | for ( var i = 0, l = objects.length; i < l; i ++ ) { 87 | 88 | var phi = Math.acos( -1 + ( 2 * i ) / l ); 89 | var theta = Math.sqrt( l * Math.PI ) * phi; 90 | 91 | var object = new THREE.Object3D(); 92 | 93 | object.position.x = 800 * Math.cos( theta ) * Math.sin( phi ); 94 | object.position.y = 800 * Math.sin( theta ) * Math.sin( phi ); 95 | object.position.z = 800 * Math.cos( phi ); 96 | 97 | vector.copy( object.position ).multiplyScalar( 2 ); 98 | 99 | object.lookAt( vector ); 100 | 101 | targets.sphere.push( object ); 102 | 103 | } 104 | 105 | // helix 106 | 107 | var vector = new THREE.Vector3(); 108 | 109 | for ( var i = 0, l = objects.length; i < l; i ++ ) { 110 | 111 | //var phi = i * 0.175 + Math.PI; 112 | var phi = i * 0.205 + Math.PI; 113 | 114 | var object = new THREE.Object3D(); 115 | 116 | object.position.x = 900 * Math.sin( phi ); 117 | object.position.y = - ( i * 8 ) + 450; 118 | object.position.z = 900 * Math.cos( phi ); 119 | 120 | vector.x = object.position.x * 2; 121 | vector.y = object.position.y; 122 | vector.z = object.position.z * 2; 123 | 124 | object.lookAt( vector ); 125 | 126 | targets.helix.push( object ); 127 | 128 | } 129 | 130 | // grid 131 | 132 | for ( var i = 0; i < objects.length; i ++ ) { 133 | 134 | var object = new THREE.Object3D(); 135 | 136 | object.position.x = ( ( i % 5 ) * 400 ) - 800; 137 | object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800; 138 | object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000; 139 | 140 | targets.grid.push( object ); 141 | 142 | } 143 | 144 | // 145 | 146 | renderer = new THREE.CSS3DRenderer(); 147 | renderer.setSize( window.innerWidth, window.innerHeight ); 148 | renderer.domElement.style.position = 'absolute'; 149 | document.getElementById( 'container' ).appendChild( renderer.domElement ); 150 | 151 | // 152 | 153 | controls = new THREE.TrackballControls( camera, renderer.domElement ); 154 | controls.rotateSpeed = 0.5; 155 | controls.minDistance = 500; 156 | controls.maxDistance = 6000; 157 | controls.addEventListener( 'change', render ); 158 | 159 | var button = document.getElementById( 'table' ); 160 | button.addEventListener( 'click', function ( event ) { 161 | 162 | transform( targets.table, 2000 ); 163 | 164 | }, false ); 165 | 166 | var button = document.getElementById( 'sphere' ); 167 | button.addEventListener( 'click', function ( event ) { 168 | 169 | transform( targets.sphere, 2000 ); 170 | 171 | }, false ); 172 | 173 | var button = document.getElementById( 'helix' ); 174 | button.addEventListener( 'click', function ( event ) { 175 | 176 | transform( targets.helix, 2000 ); 177 | 178 | }, false ); 179 | 180 | var button = document.getElementById( 'grid' ); 181 | button.addEventListener( 'click', function ( event ) { 182 | 183 | transform( targets.grid, 2000 ); 184 | 185 | }, false ); 186 | 187 | //transform( targets.helix, 5000 ); 188 | transform( targets.table, 2000 ); 189 | // 190 | 191 | window.addEventListener( 'resize', onWindowResize, false ); 192 | 193 | } 194 | 195 | function transform( targets, duration ) { 196 | 197 | TWEEN.removeAll(); 198 | 199 | for ( var i = 0; i < objects.length; i ++ ) { 200 | 201 | var object = objects[ i ]; 202 | var target = targets[ i ]; 203 | 204 | new TWEEN.Tween( object.position ) 205 | .to( { x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration ) 206 | .easing( TWEEN.Easing.Exponential.InOut ) 207 | .start(); 208 | 209 | new TWEEN.Tween( object.rotation ) 210 | .to( { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration ) 211 | .easing( TWEEN.Easing.Exponential.InOut ) 212 | .start(); 213 | 214 | } 215 | new TWEEN.Tween( this ) 216 | .to( {}, duration * 2 ) 217 | .onUpdate( render ) 218 | .start(); 219 | //*/ 220 | } 221 | 222 | function onWindowResize() { 223 | 224 | camera.aspect = window.innerWidth / window.innerHeight; 225 | camera.updateProjectionMatrix(); 226 | 227 | renderer.setSize( window.innerWidth, window.innerHeight ); 228 | 229 | } 230 | 231 | function animate() { 232 | 233 | requestAnimationFrame( animate ); 234 | 235 | TWEEN.update(); 236 | controls.update(); 237 | // render(); 238 | //move( -1 ); 239 | 240 | } 241 | 242 | function render() { 243 | /* 244 | theta += 0.1; 245 | */ 246 | 247 | /* 248 | if (!isMove) { 249 | camera.position.x += Math.PI * 1; 250 | camera.position.y += 5; 251 | camera.position.z += 0.1; 252 | } 253 | camera.lookAt( scene.position ); 254 | */ 255 | renderer.render( scene, camera ); 256 | 257 | } 258 | 259 | /** 260 | ** 控制自动旋转 261 | */ 262 | /* 263 | var isMove = false; 264 | 265 | document.onmousedown = function(e) { 266 | isMove = true; 267 | }; 268 | 269 | document.onmouseup = function() { 270 | isMove = false; 271 | animate(); 272 | } 273 | */ 274 | 275 | // 说明:用 JavaScript 实现网页图片等比例缩放 276 | function resizeImg(image,distWidth,distHeight) 277 | { 278 | srcWidth = image.width; 279 | srcHeight = image.height; 280 | var ratio = 1; 281 | if(srcWidth>0 && srcHeight>0) 282 | { 283 | if(srcWidth/srcHeight>= distWidth/distHeight) 284 | { 285 | if(srcWidth>distWidth) 286 | { 287 | ratio = distWidth/srcWidth; 288 | } 289 | } 290 | else 291 | { 292 | if(srcHeight>distHeight) 293 | { 294 | ratio = distHeight/srcHeight; 295 | } 296 | } 297 | } 298 | var width = srcWidth*ratio; 299 | var heigh = srcHeight*ratio; 300 | 301 | image.style.width = width.toString() + 'px'; 302 | image.style.height = heigh.toString() + 'px'; 303 | 304 | if(width < distWidth) 305 | image.style.paddingLeft = ((distWidth - width)/2).toString() + 'px'; 306 | 307 | if(heigh < distHeight) 308 | image.style.paddingTop = ((distHeight - heigh)/2).toString() + 'px'; 309 | } 310 | 311 | //获得指定文件夹图片名称列表,同时设置图片的位置 312 | function getImgs (uid){ 313 | 314 | $.ajax({ 315 | type: "GET", 316 | url: "/users/" + uid, 317 | //data: "", 318 | dataType: "json", 319 | success: function(data){ 320 | 321 | 322 | // console.log(data); 323 | table = data; 324 | init(); 325 | animate(); 326 | }, 327 | error: function(XMLHttpRequest, textStatus, errorThrown){ 328 | console.error(XMLHttpRequest, textStatus, errorThrown); 329 | } 330 | }); 331 | } -------------------------------------------------------------------------------- /templates/wall2.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | pper 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 30 | 31 | 399 | 400 |
401 | 409 | 410 | 411 |
412 | We like to take photo anytime, anywhere, We love Click、Click! 413 |
414 |
415 |
416 | 417 |
418 | Usage 使用方法: 419 |
    420 |
  • 1、Scan the QR Code in WeChat
    微信扫描二维码
  • 421 |
  • 2、Send Photo 发送图片
  • 422 |
  • 3、Refresh 刷新
  • 423 |
424 |
425 |
426 |
427 | 428 | 429 | -------------------------------------------------------------------------------- /public/wall2/tween.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author sole / http://soledadpenades.com 3 | * @author mrdoob / http://mrdoob.com 4 | * @author Robert Eisele / http://www.xarg.org 5 | * @author Philippe / http://philippe.elsass.me 6 | * @author Robert Penner / http://www.robertpenner.com/easing_terms_of_use.html 7 | * @author Paul Lewis / http://www.aerotwist.com/ 8 | * @author lechecacharro 9 | * @author Josh Faul / http://jocafa.com/ 10 | * @author egraether / http://egraether.com/ 11 | * @author endel / http://endel.me 12 | * @author Ben Delarre / http://delarre.net 13 | */ 14 | 15 | // Date.now shim for (ahem) Internet Explo(d|r)er 16 | if ( Date.now === undefined ) { 17 | 18 | Date.now = function () { 19 | 20 | return new Date().valueOf(); 21 | 22 | }; 23 | 24 | } 25 | 26 | var TWEEN = TWEEN || ( function () { 27 | 28 | var _tweens = []; 29 | 30 | return { 31 | 32 | REVISION: '12', 33 | 34 | getAll: function () { 35 | 36 | return _tweens; 37 | 38 | }, 39 | 40 | removeAll: function () { 41 | 42 | _tweens = []; 43 | 44 | }, 45 | 46 | add: function ( tween ) { 47 | 48 | _tweens.push( tween ); 49 | 50 | }, 51 | 52 | remove: function ( tween ) { 53 | 54 | var i = _tweens.indexOf( tween ); 55 | 56 | if ( i !== -1 ) { 57 | 58 | _tweens.splice( i, 1 ); 59 | 60 | } 61 | 62 | }, 63 | 64 | update: function ( time ) { 65 | 66 | if ( _tweens.length === 0 ) return false; 67 | 68 | var i = 0; 69 | 70 | time = time !== undefined ? time : ( typeof window !== 'undefined' && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() ); 71 | 72 | while ( i < _tweens.length ) { 73 | 74 | if ( _tweens[ i ].update( time ) ) { 75 | 76 | i++; 77 | 78 | } else { 79 | 80 | _tweens.splice( i, 1 ); 81 | 82 | } 83 | 84 | } 85 | 86 | return true; 87 | 88 | } 89 | }; 90 | 91 | } )(); 92 | 93 | TWEEN.Tween = function ( object ) { 94 | 95 | var _object = object; 96 | var _valuesStart = {}; 97 | var _valuesEnd = {}; 98 | var _valuesStartRepeat = {}; 99 | var _duration = 1000; 100 | var _repeat = 0; 101 | var _yoyo = false; 102 | var _isPlaying = false; 103 | var _reversed = false; 104 | var _delayTime = 0; 105 | var _startTime = null; 106 | var _easingFunction = TWEEN.Easing.Linear.None; 107 | var _interpolationFunction = TWEEN.Interpolation.Linear; 108 | var _chainedTweens = []; 109 | var _onStartCallback = null; 110 | var _onStartCallbackFired = false; 111 | var _onUpdateCallback = null; 112 | var _onCompleteCallback = null; 113 | 114 | // Set all starting values present on the target object 115 | for ( var field in object ) { 116 | 117 | _valuesStart[ field ] = parseFloat(object[field], 10); 118 | 119 | } 120 | 121 | this.to = function ( properties, duration ) { 122 | 123 | if ( duration !== undefined ) { 124 | 125 | _duration = duration; 126 | 127 | } 128 | 129 | _valuesEnd = properties; 130 | 131 | return this; 132 | 133 | }; 134 | 135 | this.start = function ( time ) { 136 | 137 | TWEEN.add( this ); 138 | 139 | _isPlaying = true; 140 | 141 | _onStartCallbackFired = false; 142 | 143 | _startTime = time !== undefined ? time : ( typeof window !== 'undefined' && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() ); 144 | _startTime += _delayTime; 145 | 146 | for ( var property in _valuesEnd ) { 147 | 148 | // check if an Array was provided as property value 149 | if ( _valuesEnd[ property ] instanceof Array ) { 150 | 151 | if ( _valuesEnd[ property ].length === 0 ) { 152 | 153 | continue; 154 | 155 | } 156 | 157 | // create a local copy of the Array with the start value at the front 158 | _valuesEnd[ property ] = [ _object[ property ] ].concat( _valuesEnd[ property ] ); 159 | 160 | } 161 | 162 | _valuesStart[ property ] = _object[ property ]; 163 | 164 | if( ( _valuesStart[ property ] instanceof Array ) === false ) { 165 | _valuesStart[ property ] *= 1.0; // Ensures we're using numbers, not strings 166 | } 167 | 168 | _valuesStartRepeat[ property ] = _valuesStart[ property ] || 0; 169 | 170 | } 171 | 172 | return this; 173 | 174 | }; 175 | 176 | this.stop = function () { 177 | 178 | if ( !_isPlaying ) { 179 | return this; 180 | } 181 | 182 | TWEEN.remove( this ); 183 | _isPlaying = false; 184 | this.stopChainedTweens(); 185 | return this; 186 | 187 | }; 188 | 189 | this.stopChainedTweens = function () { 190 | 191 | for ( var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i++ ) { 192 | 193 | _chainedTweens[ i ].stop(); 194 | 195 | } 196 | 197 | }; 198 | 199 | this.delay = function ( amount ) { 200 | 201 | _delayTime = amount; 202 | return this; 203 | 204 | }; 205 | 206 | this.repeat = function ( times ) { 207 | 208 | _repeat = times; 209 | return this; 210 | 211 | }; 212 | 213 | this.yoyo = function( yoyo ) { 214 | 215 | _yoyo = yoyo; 216 | return this; 217 | 218 | }; 219 | 220 | 221 | this.easing = function ( easing ) { 222 | 223 | _easingFunction = easing; 224 | return this; 225 | 226 | }; 227 | 228 | this.interpolation = function ( interpolation ) { 229 | 230 | _interpolationFunction = interpolation; 231 | return this; 232 | 233 | }; 234 | 235 | this.chain = function () { 236 | 237 | _chainedTweens = arguments; 238 | return this; 239 | 240 | }; 241 | 242 | this.onStart = function ( callback ) { 243 | 244 | _onStartCallback = callback; 245 | return this; 246 | 247 | }; 248 | 249 | this.onUpdate = function ( callback ) { 250 | 251 | _onUpdateCallback = callback; 252 | return this; 253 | 254 | }; 255 | 256 | this.onComplete = function ( callback ) { 257 | 258 | _onCompleteCallback = callback; 259 | return this; 260 | 261 | }; 262 | 263 | this.update = function ( time ) { 264 | 265 | var property; 266 | 267 | if ( time < _startTime ) { 268 | 269 | return true; 270 | 271 | } 272 | 273 | if ( _onStartCallbackFired === false ) { 274 | 275 | if ( _onStartCallback !== null ) { 276 | 277 | _onStartCallback.call( _object ); 278 | 279 | } 280 | 281 | _onStartCallbackFired = true; 282 | 283 | } 284 | 285 | var elapsed = ( time - _startTime ) / _duration; 286 | elapsed = elapsed > 1 ? 1 : elapsed; 287 | 288 | var value = _easingFunction( elapsed ); 289 | 290 | for ( property in _valuesEnd ) { 291 | 292 | var start = _valuesStart[ property ] || 0; 293 | var end = _valuesEnd[ property ]; 294 | 295 | if ( end instanceof Array ) { 296 | 297 | _object[ property ] = _interpolationFunction( end, value ); 298 | 299 | } else { 300 | 301 | // Parses relative end values with start as base (e.g.: +10, -3) 302 | if ( typeof(end) === "string" ) { 303 | end = start + parseFloat(end, 10); 304 | } 305 | 306 | // protect against non numeric properties. 307 | if ( typeof(end) === "number" ) { 308 | _object[ property ] = start + ( end - start ) * value; 309 | } 310 | 311 | } 312 | 313 | } 314 | 315 | if ( _onUpdateCallback !== null ) { 316 | 317 | _onUpdateCallback.call( _object, value ); 318 | 319 | } 320 | 321 | if ( elapsed == 1 ) { 322 | 323 | if ( _repeat > 0 ) { 324 | 325 | if( isFinite( _repeat ) ) { 326 | _repeat--; 327 | } 328 | 329 | // reassign starting values, restart by making startTime = now 330 | for( property in _valuesStartRepeat ) { 331 | 332 | if ( typeof( _valuesEnd[ property ] ) === "string" ) { 333 | _valuesStartRepeat[ property ] = _valuesStartRepeat[ property ] + parseFloat(_valuesEnd[ property ], 10); 334 | } 335 | 336 | if (_yoyo) { 337 | var tmp = _valuesStartRepeat[ property ]; 338 | _valuesStartRepeat[ property ] = _valuesEnd[ property ]; 339 | _valuesEnd[ property ] = tmp; 340 | _reversed = !_reversed; 341 | } 342 | _valuesStart[ property ] = _valuesStartRepeat[ property ]; 343 | 344 | } 345 | 346 | _startTime = time + _delayTime; 347 | 348 | return true; 349 | 350 | } else { 351 | 352 | if ( _onCompleteCallback !== null ) { 353 | 354 | _onCompleteCallback.call( _object ); 355 | 356 | } 357 | 358 | for ( var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i++ ) { 359 | 360 | _chainedTweens[ i ].start( time ); 361 | 362 | } 363 | 364 | return false; 365 | 366 | } 367 | 368 | } 369 | 370 | return true; 371 | 372 | }; 373 | 374 | }; 375 | 376 | 377 | TWEEN.Easing = { 378 | 379 | Linear: { 380 | 381 | None: function ( k ) { 382 | 383 | return k; 384 | 385 | } 386 | 387 | }, 388 | 389 | Quadratic: { 390 | 391 | In: function ( k ) { 392 | 393 | return k * k; 394 | 395 | }, 396 | 397 | Out: function ( k ) { 398 | 399 | return k * ( 2 - k ); 400 | 401 | }, 402 | 403 | InOut: function ( k ) { 404 | 405 | if ( ( k *= 2 ) < 1 ) return 0.5 * k * k; 406 | return - 0.5 * ( --k * ( k - 2 ) - 1 ); 407 | 408 | } 409 | 410 | }, 411 | 412 | Cubic: { 413 | 414 | In: function ( k ) { 415 | 416 | return k * k * k; 417 | 418 | }, 419 | 420 | Out: function ( k ) { 421 | 422 | return --k * k * k + 1; 423 | 424 | }, 425 | 426 | InOut: function ( k ) { 427 | 428 | if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k; 429 | return 0.5 * ( ( k -= 2 ) * k * k + 2 ); 430 | 431 | } 432 | 433 | }, 434 | 435 | Quartic: { 436 | 437 | In: function ( k ) { 438 | 439 | return k * k * k * k; 440 | 441 | }, 442 | 443 | Out: function ( k ) { 444 | 445 | return 1 - ( --k * k * k * k ); 446 | 447 | }, 448 | 449 | InOut: function ( k ) { 450 | 451 | if ( ( k *= 2 ) < 1) return 0.5 * k * k * k * k; 452 | return - 0.5 * ( ( k -= 2 ) * k * k * k - 2 ); 453 | 454 | } 455 | 456 | }, 457 | 458 | Quintic: { 459 | 460 | In: function ( k ) { 461 | 462 | return k * k * k * k * k; 463 | 464 | }, 465 | 466 | Out: function ( k ) { 467 | 468 | return --k * k * k * k * k + 1; 469 | 470 | }, 471 | 472 | InOut: function ( k ) { 473 | 474 | if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k * k * k; 475 | return 0.5 * ( ( k -= 2 ) * k * k * k * k + 2 ); 476 | 477 | } 478 | 479 | }, 480 | 481 | Sinusoidal: { 482 | 483 | In: function ( k ) { 484 | 485 | return 1 - Math.cos( k * Math.PI / 2 ); 486 | 487 | }, 488 | 489 | Out: function ( k ) { 490 | 491 | return Math.sin( k * Math.PI / 2 ); 492 | 493 | }, 494 | 495 | InOut: function ( k ) { 496 | 497 | return 0.5 * ( 1 - Math.cos( Math.PI * k ) ); 498 | 499 | } 500 | 501 | }, 502 | 503 | Exponential: { 504 | 505 | In: function ( k ) { 506 | 507 | return k === 0 ? 0 : Math.pow( 1024, k - 1 ); 508 | 509 | }, 510 | 511 | Out: function ( k ) { 512 | 513 | return k === 1 ? 1 : 1 - Math.pow( 2, - 10 * k ); 514 | 515 | }, 516 | 517 | InOut: function ( k ) { 518 | 519 | if ( k === 0 ) return 0; 520 | if ( k === 1 ) return 1; 521 | if ( ( k *= 2 ) < 1 ) return 0.5 * Math.pow( 1024, k - 1 ); 522 | return 0.5 * ( - Math.pow( 2, - 10 * ( k - 1 ) ) + 2 ); 523 | 524 | } 525 | 526 | }, 527 | 528 | Circular: { 529 | 530 | In: function ( k ) { 531 | 532 | return 1 - Math.sqrt( 1 - k * k ); 533 | 534 | }, 535 | 536 | Out: function ( k ) { 537 | 538 | return Math.sqrt( 1 - ( --k * k ) ); 539 | 540 | }, 541 | 542 | InOut: function ( k ) { 543 | 544 | if ( ( k *= 2 ) < 1) return - 0.5 * ( Math.sqrt( 1 - k * k) - 1); 545 | return 0.5 * ( Math.sqrt( 1 - ( k -= 2) * k) + 1); 546 | 547 | } 548 | 549 | }, 550 | 551 | Elastic: { 552 | 553 | In: function ( k ) { 554 | 555 | var s, a = 0.1, p = 0.4; 556 | if ( k === 0 ) return 0; 557 | if ( k === 1 ) return 1; 558 | if ( !a || a < 1 ) { a = 1; s = p / 4; } 559 | else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); 560 | return - ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) ); 561 | 562 | }, 563 | 564 | Out: function ( k ) { 565 | 566 | var s, a = 0.1, p = 0.4; 567 | if ( k === 0 ) return 0; 568 | if ( k === 1 ) return 1; 569 | if ( !a || a < 1 ) { a = 1; s = p / 4; } 570 | else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); 571 | return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 ); 572 | 573 | }, 574 | 575 | InOut: function ( k ) { 576 | 577 | var s, a = 0.1, p = 0.4; 578 | if ( k === 0 ) return 0; 579 | if ( k === 1 ) return 1; 580 | if ( !a || a < 1 ) { a = 1; s = p / 4; } 581 | else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); 582 | if ( ( k *= 2 ) < 1 ) return - 0.5 * ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) ); 583 | return a * Math.pow( 2, -10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) * 0.5 + 1; 584 | 585 | } 586 | 587 | }, 588 | 589 | Back: { 590 | 591 | In: function ( k ) { 592 | 593 | var s = 1.70158; 594 | return k * k * ( ( s + 1 ) * k - s ); 595 | 596 | }, 597 | 598 | Out: function ( k ) { 599 | 600 | var s = 1.70158; 601 | return --k * k * ( ( s + 1 ) * k + s ) + 1; 602 | 603 | }, 604 | 605 | InOut: function ( k ) { 606 | 607 | var s = 1.70158 * 1.525; 608 | if ( ( k *= 2 ) < 1 ) return 0.5 * ( k * k * ( ( s + 1 ) * k - s ) ); 609 | return 0.5 * ( ( k -= 2 ) * k * ( ( s + 1 ) * k + s ) + 2 ); 610 | 611 | } 612 | 613 | }, 614 | 615 | Bounce: { 616 | 617 | In: function ( k ) { 618 | 619 | return 1 - TWEEN.Easing.Bounce.Out( 1 - k ); 620 | 621 | }, 622 | 623 | Out: function ( k ) { 624 | 625 | if ( k < ( 1 / 2.75 ) ) { 626 | 627 | return 7.5625 * k * k; 628 | 629 | } else if ( k < ( 2 / 2.75 ) ) { 630 | 631 | return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75; 632 | 633 | } else if ( k < ( 2.5 / 2.75 ) ) { 634 | 635 | return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375; 636 | 637 | } else { 638 | 639 | return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375; 640 | 641 | } 642 | 643 | }, 644 | 645 | InOut: function ( k ) { 646 | 647 | if ( k < 0.5 ) return TWEEN.Easing.Bounce.In( k * 2 ) * 0.5; 648 | return TWEEN.Easing.Bounce.Out( k * 2 - 1 ) * 0.5 + 0.5; 649 | 650 | } 651 | 652 | } 653 | 654 | }; 655 | 656 | TWEEN.Interpolation = { 657 | 658 | Linear: function ( v, k ) { 659 | 660 | var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = TWEEN.Interpolation.Utils.Linear; 661 | 662 | if ( k < 0 ) return fn( v[ 0 ], v[ 1 ], f ); 663 | if ( k > 1 ) return fn( v[ m ], v[ m - 1 ], m - f ); 664 | 665 | return fn( v[ i ], v[ i + 1 > m ? m : i + 1 ], f - i ); 666 | 667 | }, 668 | 669 | Bezier: function ( v, k ) { 670 | 671 | var b = 0, n = v.length - 1, pw = Math.pow, bn = TWEEN.Interpolation.Utils.Bernstein, i; 672 | 673 | for ( i = 0; i <= n; i++ ) { 674 | b += pw( 1 - k, n - i ) * pw( k, i ) * v[ i ] * bn( n, i ); 675 | } 676 | 677 | return b; 678 | 679 | }, 680 | 681 | CatmullRom: function ( v, k ) { 682 | 683 | var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = TWEEN.Interpolation.Utils.CatmullRom; 684 | 685 | if ( v[ 0 ] === v[ m ] ) { 686 | 687 | if ( k < 0 ) i = Math.floor( f = m * ( 1 + k ) ); 688 | 689 | return fn( v[ ( i - 1 + m ) % m ], v[ i ], v[ ( i + 1 ) % m ], v[ ( i + 2 ) % m ], f - i ); 690 | 691 | } else { 692 | 693 | if ( k < 0 ) return v[ 0 ] - ( fn( v[ 0 ], v[ 0 ], v[ 1 ], v[ 1 ], -f ) - v[ 0 ] ); 694 | if ( k > 1 ) return v[ m ] - ( fn( v[ m ], v[ m ], v[ m - 1 ], v[ m - 1 ], f - m ) - v[ m ] ); 695 | 696 | return fn( v[ i ? i - 1 : 0 ], v[ i ], v[ m < i + 1 ? m : i + 1 ], v[ m < i + 2 ? m : i + 2 ], f - i ); 697 | 698 | } 699 | 700 | }, 701 | 702 | Utils: { 703 | 704 | Linear: function ( p0, p1, t ) { 705 | 706 | return ( p1 - p0 ) * t + p0; 707 | 708 | }, 709 | 710 | Bernstein: function ( n , i ) { 711 | 712 | var fc = TWEEN.Interpolation.Utils.Factorial; 713 | return fc( n ) / fc( i ) / fc( n - i ); 714 | 715 | }, 716 | 717 | Factorial: ( function () { 718 | 719 | var a = [ 1 ]; 720 | 721 | return function ( n ) { 722 | 723 | var s = 1, i; 724 | if ( a[ n ] ) return a[ n ]; 725 | for ( i = n; i > 1; i-- ) s *= i; 726 | return a[ n ] = s; 727 | 728 | }; 729 | 730 | } )(), 731 | 732 | CatmullRom: function ( p0, p1, p2, p3, t ) { 733 | 734 | var v0 = ( p2 - p0 ) * 0.5, v1 = ( p3 - p1 ) * 0.5, t2 = t * t, t3 = t * t2; 735 | return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; 736 | 737 | } 738 | 739 | } 740 | 741 | }; 742 | -------------------------------------------------------------------------------- /public/js/TrackballControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Eberhard Graether / http://egraether.com/ 3 | * @author Mark Lundin / http://mark-lundin.com 4 | * @author Simone Manini / http://daron1337.github.io 5 | * @author Luca Antiga / http://lantiga.github.io 6 | */ 7 | 8 | THREE.TrackballControls = function ( object, domElement ) { 9 | 10 | var _this = this; 11 | var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; 12 | 13 | this.object = object; 14 | this.domElement = ( domElement !== undefined ) ? domElement : document; 15 | 16 | // API 17 | 18 | this.enabled = true; 19 | 20 | this.screen = { left: 0, top: 0, width: 0, height: 0 }; 21 | 22 | this.rotateSpeed = 1.0; 23 | this.zoomSpeed = 1.2; 24 | this.panSpeed = 0.3; 25 | 26 | this.noRotate = false; 27 | this.noZoom = false; 28 | this.noPan = false; 29 | 30 | this.staticMoving = false; 31 | this.dynamicDampingFactor = 0.2; 32 | 33 | this.minDistance = 0; 34 | this.maxDistance = Infinity; 35 | 36 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; 37 | 38 | // internals 39 | 40 | this.target = new THREE.Vector3(); 41 | 42 | var EPS = 0.000001; 43 | 44 | var lastPosition = new THREE.Vector3(); 45 | 46 | var _state = STATE.NONE, 47 | _prevState = STATE.NONE, 48 | 49 | _eye = new THREE.Vector3(), 50 | 51 | _movePrev = new THREE.Vector2(), 52 | _moveCurr = new THREE.Vector2(), 53 | 54 | _lastAxis = new THREE.Vector3(), 55 | _lastAngle = 0, 56 | 57 | _zoomStart = new THREE.Vector2(), 58 | _zoomEnd = new THREE.Vector2(), 59 | 60 | _touchZoomDistanceStart = 0, 61 | _touchZoomDistanceEnd = 0, 62 | 63 | _panStart = new THREE.Vector2(), 64 | _panEnd = new THREE.Vector2(); 65 | 66 | // for reset 67 | 68 | this.target0 = this.target.clone(); 69 | this.position0 = this.object.position.clone(); 70 | this.up0 = this.object.up.clone(); 71 | 72 | // events 73 | 74 | var changeEvent = { type: 'change' }; 75 | var startEvent = { type: 'start' }; 76 | var endEvent = { type: 'end' }; 77 | 78 | 79 | // methods 80 | 81 | this.handleResize = function () { 82 | 83 | if ( this.domElement === document ) { 84 | 85 | this.screen.left = 0; 86 | this.screen.top = 0; 87 | this.screen.width = window.innerWidth; 88 | this.screen.height = window.innerHeight; 89 | 90 | } else { 91 | 92 | var box = this.domElement.getBoundingClientRect(); 93 | // adjustments come from similar code in the jquery offset() function 94 | var d = this.domElement.ownerDocument.documentElement; 95 | this.screen.left = box.left + window.pageXOffset - d.clientLeft; 96 | this.screen.top = box.top + window.pageYOffset - d.clientTop; 97 | this.screen.width = box.width; 98 | this.screen.height = box.height; 99 | 100 | } 101 | 102 | }; 103 | 104 | var getMouseOnScreen = ( function () { 105 | 106 | var vector = new THREE.Vector2(); 107 | 108 | return function getMouseOnScreen( pageX, pageY ) { 109 | 110 | vector.set( 111 | ( pageX - _this.screen.left ) / _this.screen.width, 112 | ( pageY - _this.screen.top ) / _this.screen.height 113 | ); 114 | 115 | return vector; 116 | 117 | }; 118 | 119 | }() ); 120 | 121 | var getMouseOnCircle = ( function () { 122 | 123 | var vector = new THREE.Vector2(); 124 | 125 | return function getMouseOnCircle( pageX, pageY ) { 126 | 127 | vector.set( 128 | ( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ), 129 | ( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional 130 | ); 131 | 132 | return vector; 133 | 134 | }; 135 | 136 | }() ); 137 | 138 | this.rotateCamera = ( function () { 139 | 140 | var axis = new THREE.Vector3(), 141 | quaternion = new THREE.Quaternion(), 142 | eyeDirection = new THREE.Vector3(), 143 | objectUpDirection = new THREE.Vector3(), 144 | objectSidewaysDirection = new THREE.Vector3(), 145 | moveDirection = new THREE.Vector3(), 146 | angle; 147 | 148 | return function rotateCamera() { 149 | 150 | moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); 151 | angle = moveDirection.length(); 152 | 153 | if ( angle ) { 154 | 155 | _eye.copy( _this.object.position ).sub( _this.target ); 156 | 157 | eyeDirection.copy( _eye ).normalize(); 158 | objectUpDirection.copy( _this.object.up ).normalize(); 159 | objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize(); 160 | 161 | objectUpDirection.setLength( _moveCurr.y - _movePrev.y ); 162 | objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x ); 163 | 164 | moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) ); 165 | 166 | axis.crossVectors( moveDirection, _eye ).normalize(); 167 | 168 | angle *= _this.rotateSpeed; 169 | quaternion.setFromAxisAngle( axis, angle ); 170 | 171 | _eye.applyQuaternion( quaternion ); 172 | _this.object.up.applyQuaternion( quaternion ); 173 | 174 | _lastAxis.copy( axis ); 175 | _lastAngle = angle; 176 | 177 | } else if ( ! _this.staticMoving && _lastAngle ) { 178 | 179 | _lastAngle *= Math.sqrt( 1.0 - _this.dynamicDampingFactor ); 180 | _eye.copy( _this.object.position ).sub( _this.target ); 181 | quaternion.setFromAxisAngle( _lastAxis, _lastAngle ); 182 | _eye.applyQuaternion( quaternion ); 183 | _this.object.up.applyQuaternion( quaternion ); 184 | 185 | } 186 | 187 | _movePrev.copy( _moveCurr ); 188 | 189 | }; 190 | 191 | }() ); 192 | 193 | 194 | this.zoomCamera = function () { 195 | 196 | var factor; 197 | 198 | if ( _state === STATE.TOUCH_ZOOM_PAN ) { 199 | 200 | factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; 201 | _touchZoomDistanceStart = _touchZoomDistanceEnd; 202 | _eye.multiplyScalar( factor ); 203 | 204 | } else { 205 | 206 | factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; 207 | 208 | if ( factor !== 1.0 && factor > 0.0 ) { 209 | 210 | _eye.multiplyScalar( factor ); 211 | 212 | } 213 | 214 | if ( _this.staticMoving ) { 215 | 216 | _zoomStart.copy( _zoomEnd ); 217 | 218 | } else { 219 | 220 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; 221 | 222 | } 223 | 224 | } 225 | 226 | }; 227 | 228 | this.panCamera = ( function () { 229 | 230 | var mouseChange = new THREE.Vector2(), 231 | objectUp = new THREE.Vector3(), 232 | pan = new THREE.Vector3(); 233 | 234 | return function panCamera() { 235 | 236 | mouseChange.copy( _panEnd ).sub( _panStart ); 237 | 238 | if ( mouseChange.lengthSq() ) { 239 | 240 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); 241 | 242 | pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x ); 243 | pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) ); 244 | 245 | _this.object.position.add( pan ); 246 | _this.target.add( pan ); 247 | 248 | if ( _this.staticMoving ) { 249 | 250 | _panStart.copy( _panEnd ); 251 | 252 | } else { 253 | 254 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) ); 255 | 256 | } 257 | 258 | } 259 | 260 | }; 261 | 262 | }() ); 263 | 264 | this.checkDistances = function () { 265 | 266 | if ( ! _this.noZoom || ! _this.noPan ) { 267 | 268 | if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) { 269 | 270 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) ); 271 | _zoomStart.copy( _zoomEnd ); 272 | 273 | } 274 | 275 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) { 276 | 277 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) ); 278 | _zoomStart.copy( _zoomEnd ); 279 | 280 | } 281 | 282 | } 283 | 284 | }; 285 | 286 | this.update = function () { 287 | 288 | _eye.subVectors( _this.object.position, _this.target ); 289 | 290 | if ( ! _this.noRotate ) { 291 | 292 | _this.rotateCamera(); 293 | 294 | } 295 | 296 | if ( ! _this.noZoom ) { 297 | 298 | _this.zoomCamera(); 299 | 300 | } 301 | 302 | if ( ! _this.noPan ) { 303 | 304 | _this.panCamera(); 305 | 306 | } 307 | 308 | _this.object.position.addVectors( _this.target, _eye ); 309 | 310 | _this.checkDistances(); 311 | 312 | _this.object.lookAt( _this.target ); 313 | 314 | if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) { 315 | 316 | _this.dispatchEvent( changeEvent ); 317 | 318 | lastPosition.copy( _this.object.position ); 319 | 320 | } 321 | 322 | }; 323 | 324 | this.reset = function () { 325 | 326 | _state = STATE.NONE; 327 | _prevState = STATE.NONE; 328 | 329 | _this.target.copy( _this.target0 ); 330 | _this.object.position.copy( _this.position0 ); 331 | _this.object.up.copy( _this.up0 ); 332 | 333 | _eye.subVectors( _this.object.position, _this.target ); 334 | 335 | _this.object.lookAt( _this.target ); 336 | 337 | _this.dispatchEvent( changeEvent ); 338 | 339 | lastPosition.copy( _this.object.position ); 340 | 341 | }; 342 | 343 | // listeners 344 | 345 | function keydown( event ) { 346 | 347 | if ( _this.enabled === false ) return; 348 | 349 | window.removeEventListener( 'keydown', keydown ); 350 | 351 | _prevState = _state; 352 | 353 | if ( _state !== STATE.NONE ) { 354 | 355 | return; 356 | 357 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) { 358 | 359 | _state = STATE.ROTATE; 360 | 361 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) { 362 | 363 | _state = STATE.ZOOM; 364 | 365 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) { 366 | 367 | _state = STATE.PAN; 368 | 369 | } 370 | 371 | } 372 | 373 | function keyup( event ) { 374 | 375 | if ( _this.enabled === false ) return; 376 | 377 | _state = _prevState; 378 | 379 | window.addEventListener( 'keydown', keydown, false ); 380 | 381 | } 382 | 383 | function mousedown( event ) { 384 | 385 | if ( _this.enabled === false ) return; 386 | 387 | event.preventDefault(); 388 | event.stopPropagation(); 389 | 390 | if ( _state === STATE.NONE ) { 391 | 392 | _state = event.button; 393 | 394 | } 395 | 396 | if ( _state === STATE.ROTATE && ! _this.noRotate ) { 397 | 398 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); 399 | _movePrev.copy( _moveCurr ); 400 | 401 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) { 402 | 403 | _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 404 | _zoomEnd.copy( _zoomStart ); 405 | 406 | } else if ( _state === STATE.PAN && ! _this.noPan ) { 407 | 408 | _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 409 | _panEnd.copy( _panStart ); 410 | 411 | } 412 | 413 | document.addEventListener( 'mousemove', mousemove, false ); 414 | document.addEventListener( 'mouseup', mouseup, false ); 415 | 416 | _this.dispatchEvent( startEvent ); 417 | 418 | } 419 | 420 | function mousemove( event ) { 421 | 422 | if ( _this.enabled === false ) return; 423 | 424 | event.preventDefault(); 425 | event.stopPropagation(); 426 | 427 | if ( _state === STATE.ROTATE && ! _this.noRotate ) { 428 | 429 | _movePrev.copy( _moveCurr ); 430 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); 431 | 432 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) { 433 | 434 | _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 435 | 436 | } else if ( _state === STATE.PAN && ! _this.noPan ) { 437 | 438 | _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 439 | 440 | } 441 | 442 | } 443 | 444 | function mouseup( event ) { 445 | 446 | if ( _this.enabled === false ) return; 447 | 448 | event.preventDefault(); 449 | event.stopPropagation(); 450 | 451 | _state = STATE.NONE; 452 | 453 | document.removeEventListener( 'mousemove', mousemove ); 454 | document.removeEventListener( 'mouseup', mouseup ); 455 | _this.dispatchEvent( endEvent ); 456 | 457 | } 458 | 459 | function mousewheel( event ) { 460 | 461 | if ( _this.enabled === false ) return; 462 | 463 | if ( _this.noZoom === true ) return; 464 | 465 | event.preventDefault(); 466 | event.stopPropagation(); 467 | 468 | switch ( event.deltaMode ) { 469 | 470 | case 2: 471 | // Zoom in pages 472 | _zoomStart.y -= event.deltaY * 0.025; 473 | break; 474 | 475 | case 1: 476 | // Zoom in lines 477 | _zoomStart.y -= event.deltaY * 0.01; 478 | break; 479 | 480 | default: 481 | // undefined, 0, assume pixels 482 | _zoomStart.y -= event.deltaY * 0.00025; 483 | break; 484 | 485 | } 486 | 487 | _this.dispatchEvent( startEvent ); 488 | _this.dispatchEvent( endEvent ); 489 | 490 | } 491 | 492 | function touchstart( event ) { 493 | 494 | if ( _this.enabled === false ) return; 495 | 496 | event.preventDefault(); 497 | 498 | switch ( event.touches.length ) { 499 | 500 | case 1: 501 | _state = STATE.TOUCH_ROTATE; 502 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 503 | _movePrev.copy( _moveCurr ); 504 | break; 505 | 506 | default: // 2 or more 507 | _state = STATE.TOUCH_ZOOM_PAN; 508 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 509 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 510 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); 511 | 512 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 513 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 514 | _panStart.copy( getMouseOnScreen( x, y ) ); 515 | _panEnd.copy( _panStart ); 516 | break; 517 | 518 | } 519 | 520 | _this.dispatchEvent( startEvent ); 521 | 522 | } 523 | 524 | function touchmove( event ) { 525 | 526 | if ( _this.enabled === false ) return; 527 | 528 | event.preventDefault(); 529 | event.stopPropagation(); 530 | 531 | switch ( event.touches.length ) { 532 | 533 | case 1: 534 | _movePrev.copy( _moveCurr ); 535 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 536 | break; 537 | 538 | default: // 2 or more 539 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 540 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 541 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); 542 | 543 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 544 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 545 | _panEnd.copy( getMouseOnScreen( x, y ) ); 546 | break; 547 | 548 | } 549 | 550 | } 551 | 552 | function touchend( event ) { 553 | 554 | if ( _this.enabled === false ) return; 555 | 556 | switch ( event.touches.length ) { 557 | 558 | case 0: 559 | _state = STATE.NONE; 560 | break; 561 | 562 | case 1: 563 | _state = STATE.TOUCH_ROTATE; 564 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 565 | _movePrev.copy( _moveCurr ); 566 | break; 567 | 568 | } 569 | 570 | _this.dispatchEvent( endEvent ); 571 | 572 | } 573 | 574 | function contextmenu( event ) { 575 | 576 | if ( _this.enabled === false ) return; 577 | 578 | event.preventDefault(); 579 | 580 | } 581 | 582 | this.dispose = function () { 583 | 584 | this.domElement.removeEventListener( 'contextmenu', contextmenu, false ); 585 | this.domElement.removeEventListener( 'mousedown', mousedown, false ); 586 | this.domElement.removeEventListener( 'wheel', mousewheel, false ); 587 | 588 | this.domElement.removeEventListener( 'touchstart', touchstart, false ); 589 | this.domElement.removeEventListener( 'touchend', touchend, false ); 590 | this.domElement.removeEventListener( 'touchmove', touchmove, false ); 591 | 592 | document.removeEventListener( 'mousemove', mousemove, false ); 593 | document.removeEventListener( 'mouseup', mouseup, false ); 594 | 595 | window.removeEventListener( 'keydown', keydown, false ); 596 | window.removeEventListener( 'keyup', keyup, false ); 597 | 598 | }; 599 | 600 | this.domElement.addEventListener( 'contextmenu', contextmenu, false ); 601 | this.domElement.addEventListener( 'mousedown', mousedown, false ); 602 | this.domElement.addEventListener( 'wheel', mousewheel, false ); 603 | 604 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 605 | this.domElement.addEventListener( 'touchend', touchend, false ); 606 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 607 | 608 | window.addEventListener( 'keydown', keydown, false ); 609 | window.addEventListener( 'keyup', keyup, false ); 610 | 611 | this.handleResize(); 612 | 613 | // force an update at start 614 | this.update(); 615 | 616 | }; 617 | 618 | THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 619 | THREE.TrackballControls.prototype.constructor = THREE.TrackballControls; 620 | -------------------------------------------------------------------------------- /public/wall2/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | /*global THREE, console */ 9 | 10 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains 11 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is 12 | // supported. 13 | // 14 | // Orbit - left mouse / touch: one finger move 15 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 16 | // Pan - right mouse, or arrow keys / touch: three finter swipe 17 | // 18 | // This is a drop-in replacement for (most) TrackballControls used in examples. 19 | // That is, include this js file and wherever you see: 20 | // controls = new THREE.TrackballControls( camera ); 21 | // controls.target.z = 150; 22 | // Simple substitute "OrbitControls" and the control should work as-is. 23 | 24 | THREE.OrbitControls = function ( object, domElement ) { 25 | 26 | this.object = object; 27 | this.domElement = ( domElement !== undefined ) ? domElement : document; 28 | 29 | // API 30 | 31 | // Set to false to disable this control 32 | this.enabled = true; 33 | 34 | // "target" sets the location of focus, where the control orbits around 35 | // and where it pans with respect to. 36 | this.target = new THREE.Vector3(); 37 | // center is old, deprecated; use "target" instead 38 | this.center = this.target; 39 | 40 | // This option actually enables dollying in and out; left as "zoom" for 41 | // backwards compatibility 42 | this.noZoom = false; 43 | this.zoomSpeed = 1.0; 44 | // Limits to how far you can dolly in and out 45 | this.minDistance = 0; 46 | this.maxDistance = Infinity; 47 | 48 | // Set to true to disable this control 49 | this.noRotate = false; 50 | this.rotateSpeed = 1.0; 51 | 52 | // Set to true to disable this control 53 | this.noPan = false; 54 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 55 | 56 | // Set to true to automatically rotate around the target 57 | this.autoRotate = false; 58 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 59 | 60 | // How far you can orbit vertically, upper and lower limits. 61 | // Range is 0 to Math.PI radians. 62 | this.minPolarAngle = 0; // radians 63 | this.maxPolarAngle = Math.PI; // radians 64 | 65 | // Set to true to disable use of the keys 66 | this.noKeys = false; 67 | // The four arrow keys 68 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 69 | 70 | //////////// 71 | // internals 72 | 73 | var scope = this; 74 | 75 | var EPS = 0.000001; 76 | 77 | var rotateStart = new THREE.Vector2(); 78 | var rotateEnd = new THREE.Vector2(); 79 | var rotateDelta = new THREE.Vector2(); 80 | 81 | var panStart = new THREE.Vector2(); 82 | var panEnd = new THREE.Vector2(); 83 | var panDelta = new THREE.Vector2(); 84 | 85 | var dollyStart = new THREE.Vector2(); 86 | var dollyEnd = new THREE.Vector2(); 87 | var dollyDelta = new THREE.Vector2(); 88 | 89 | var phiDelta = 0; 90 | var thetaDelta = 0; 91 | var scale = 1; 92 | var pan = new THREE.Vector3(); 93 | 94 | var lastPosition = new THREE.Vector3(); 95 | 96 | var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 97 | var state = STATE.NONE; 98 | 99 | // events 100 | 101 | var changeEvent = { type: 'change' }; 102 | 103 | 104 | this.rotateLeft = function ( angle ) { 105 | 106 | if ( angle === undefined ) { 107 | 108 | angle = getAutoRotationAngle(); 109 | 110 | } 111 | 112 | thetaDelta -= angle; 113 | 114 | }; 115 | 116 | this.rotateUp = function ( angle ) { 117 | 118 | if ( angle === undefined ) { 119 | 120 | angle = getAutoRotationAngle(); 121 | 122 | } 123 | 124 | phiDelta -= angle; 125 | 126 | }; 127 | 128 | // pass in distance in world space to move left 129 | this.panLeft = function ( distance ) { 130 | 131 | var panOffset = new THREE.Vector3(); 132 | var te = this.object.matrix.elements; 133 | // get X column of matrix 134 | panOffset.set( te[0], te[1], te[2] ); 135 | panOffset.multiplyScalar(-distance); 136 | 137 | pan.add( panOffset ); 138 | 139 | }; 140 | 141 | // pass in distance in world space to move up 142 | this.panUp = function ( distance ) { 143 | 144 | var panOffset = new THREE.Vector3(); 145 | var te = this.object.matrix.elements; 146 | // get Y column of matrix 147 | panOffset.set( te[4], te[5], te[6] ); 148 | panOffset.multiplyScalar(distance); 149 | 150 | pan.add( panOffset ); 151 | }; 152 | 153 | // main entry point; pass in Vector2 of change desired in pixel space, 154 | // right and down are positive 155 | this.pan = function ( delta ) { 156 | 157 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 158 | 159 | if ( scope.object.fov !== undefined ) { 160 | 161 | // perspective 162 | var position = scope.object.position; 163 | var offset = position.clone().sub( scope.target ); 164 | var targetDistance = offset.length(); 165 | 166 | // half of the fov is center to top of screen 167 | targetDistance *= Math.tan( (scope.object.fov/2) * Math.PI / 180.0 ); 168 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 169 | scope.panLeft( 2 * delta.x * targetDistance / element.clientHeight ); 170 | scope.panUp( 2 * delta.y * targetDistance / element.clientHeight ); 171 | 172 | } else if ( scope.object.top !== undefined ) { 173 | 174 | // orthographic 175 | scope.panLeft( delta.x * (scope.object.right - scope.object.left) / element.clientWidth ); 176 | scope.panUp( delta.y * (scope.object.top - scope.object.bottom) / element.clientHeight ); 177 | 178 | } else { 179 | 180 | // camera neither orthographic or perspective - warn user 181 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 182 | 183 | } 184 | 185 | }; 186 | 187 | this.dollyIn = function ( dollyScale ) { 188 | 189 | if ( dollyScale === undefined ) { 190 | 191 | dollyScale = getZoomScale(); 192 | 193 | } 194 | 195 | scale /= dollyScale; 196 | 197 | }; 198 | 199 | this.dollyOut = function ( dollyScale ) { 200 | 201 | if ( dollyScale === undefined ) { 202 | 203 | dollyScale = getZoomScale(); 204 | 205 | } 206 | 207 | scale *= dollyScale; 208 | 209 | }; 210 | 211 | this.update = function () { 212 | 213 | var position = this.object.position; 214 | var offset = position.clone().sub( this.target ); 215 | 216 | // angle from z-axis around y-axis 217 | 218 | var theta = Math.atan2( offset.x, offset.z ); 219 | 220 | // angle from y-axis 221 | 222 | var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 223 | 224 | if ( this.autoRotate ) { 225 | 226 | this.rotateLeft( getAutoRotationAngle() ); 227 | 228 | } 229 | 230 | theta += thetaDelta; 231 | phi += phiDelta; 232 | 233 | // restrict phi to be between desired limits 234 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); 235 | 236 | // restrict phi to be betwee EPS and PI-EPS 237 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 238 | 239 | var radius = offset.length() * scale; 240 | 241 | // restrict radius to be between desired limits 242 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); 243 | 244 | // move target to panned location 245 | this.target.add( pan ); 246 | 247 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 248 | offset.y = radius * Math.cos( phi ); 249 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 250 | 251 | position.copy( this.target ).add( offset ); 252 | 253 | this.object.lookAt( this.target ); 254 | 255 | thetaDelta = 0; 256 | phiDelta = 0; 257 | scale = 1; 258 | pan.set(0,0,0); 259 | 260 | if ( lastPosition.distanceTo( this.object.position ) > 0 ) { 261 | 262 | this.dispatchEvent( changeEvent ); 263 | 264 | lastPosition.copy( this.object.position ); 265 | 266 | } 267 | 268 | }; 269 | 270 | 271 | function getAutoRotationAngle() { 272 | 273 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 274 | 275 | } 276 | 277 | function getZoomScale() { 278 | 279 | return Math.pow( 0.95, scope.zoomSpeed ); 280 | 281 | } 282 | 283 | function onMouseDown( event ) { 284 | 285 | if ( scope.enabled === false ) { return; } 286 | event.preventDefault(); 287 | 288 | if ( event.button === 0 ) { 289 | if ( scope.noRotate === true ) { return; } 290 | 291 | state = STATE.ROTATE; 292 | 293 | rotateStart.set( event.clientX, event.clientY ); 294 | 295 | } else if ( event.button === 1 ) { 296 | if ( scope.noZoom === true ) { return; } 297 | 298 | state = STATE.DOLLY; 299 | 300 | dollyStart.set( event.clientX, event.clientY ); 301 | 302 | } else if ( event.button === 2 ) { 303 | if ( scope.noPan === true ) { return; } 304 | 305 | state = STATE.PAN; 306 | 307 | panStart.set( event.clientX, event.clientY ); 308 | 309 | } 310 | 311 | // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be 312 | scope.domElement.addEventListener( 'mousemove', onMouseMove, false ); 313 | scope.domElement.addEventListener( 'mouseup', onMouseUp, false ); 314 | 315 | } 316 | 317 | function onMouseMove( event ) { 318 | 319 | if ( scope.enabled === false ) return; 320 | 321 | event.preventDefault(); 322 | 323 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 324 | 325 | if ( state === STATE.ROTATE ) { 326 | 327 | if ( scope.noRotate === true ) return; 328 | 329 | rotateEnd.set( event.clientX, event.clientY ); 330 | rotateDelta.subVectors( rotateEnd, rotateStart ); 331 | 332 | // rotating across whole screen goes 360 degrees around 333 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 334 | // rotating up and down along whole screen attempts to go 360, but limited to 180 335 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 336 | 337 | rotateStart.copy( rotateEnd ); 338 | 339 | } else if ( state === STATE.DOLLY ) { 340 | 341 | if ( scope.noZoom === true ) return; 342 | 343 | dollyEnd.set( event.clientX, event.clientY ); 344 | dollyDelta.subVectors( dollyEnd, dollyStart ); 345 | 346 | if ( dollyDelta.y > 0 ) { 347 | 348 | scope.dollyIn(); 349 | 350 | } else { 351 | 352 | scope.dollyOut(); 353 | 354 | } 355 | 356 | dollyStart.copy( dollyEnd ); 357 | 358 | } else if ( state === STATE.PAN ) { 359 | 360 | if ( scope.noPan === true ) return; 361 | 362 | panEnd.set( event.clientX, event.clientY ); 363 | panDelta.subVectors( panEnd, panStart ); 364 | 365 | scope.pan( panDelta ); 366 | 367 | panStart.copy( panEnd ); 368 | 369 | } 370 | 371 | // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be 372 | scope.update(); 373 | 374 | } 375 | 376 | function onMouseUp( /* event */ ) { 377 | 378 | if ( scope.enabled === false ) return; 379 | 380 | // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be 381 | scope.domElement.removeEventListener( 'mousemove', onMouseMove, false ); 382 | scope.domElement.removeEventListener( 'mouseup', onMouseUp, false ); 383 | 384 | state = STATE.NONE; 385 | 386 | } 387 | 388 | function onMouseWheel( event ) { 389 | 390 | if ( scope.enabled === false || scope.noZoom === true ) return; 391 | 392 | var delta = 0; 393 | 394 | if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 395 | 396 | delta = event.wheelDelta; 397 | 398 | } else if ( event.detail ) { // Firefox 399 | 400 | delta = - event.detail; 401 | 402 | } 403 | 404 | if ( delta > 0 ) { 405 | 406 | scope.dollyOut(); 407 | 408 | } else { 409 | 410 | scope.dollyIn(); 411 | 412 | } 413 | 414 | } 415 | 416 | function onKeyDown( event ) { 417 | 418 | if ( scope.enabled === false ) { return; } 419 | if ( scope.noKeys === true ) { return; } 420 | if ( scope.noPan === true ) { return; } 421 | 422 | // pan a pixel - I guess for precise positioning? 423 | // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be 424 | var needUpdate = false; 425 | 426 | switch ( event.keyCode ) { 427 | 428 | case scope.keys.UP: 429 | scope.pan( new THREE.Vector2( 0, scope.keyPanSpeed ) ); 430 | needUpdate = true; 431 | break; 432 | case scope.keys.BOTTOM: 433 | scope.pan( new THREE.Vector2( 0, -scope.keyPanSpeed ) ); 434 | needUpdate = true; 435 | break; 436 | case scope.keys.LEFT: 437 | scope.pan( new THREE.Vector2( scope.keyPanSpeed, 0 ) ); 438 | needUpdate = true; 439 | break; 440 | case scope.keys.RIGHT: 441 | scope.pan( new THREE.Vector2( -scope.keyPanSpeed, 0 ) ); 442 | needUpdate = true; 443 | break; 444 | } 445 | 446 | // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be 447 | if ( needUpdate ) { 448 | 449 | scope.update(); 450 | 451 | } 452 | 453 | } 454 | 455 | function touchstart( event ) { 456 | 457 | if ( scope.enabled === false ) { return; } 458 | 459 | switch ( event.touches.length ) { 460 | 461 | case 1: // one-fingered touch: rotate 462 | if ( scope.noRotate === true ) { return; } 463 | 464 | state = STATE.TOUCH_ROTATE; 465 | 466 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 467 | break; 468 | 469 | case 2: // two-fingered touch: dolly 470 | if ( scope.noZoom === true ) { return; } 471 | 472 | state = STATE.TOUCH_DOLLY; 473 | 474 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 475 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 476 | var distance = Math.sqrt( dx * dx + dy * dy ); 477 | dollyStart.set( 0, distance ); 478 | break; 479 | 480 | case 3: // three-fingered touch: pan 481 | if ( scope.noPan === true ) { return; } 482 | 483 | state = STATE.TOUCH_PAN; 484 | 485 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 486 | break; 487 | 488 | default: 489 | state = STATE.NONE; 490 | 491 | } 492 | } 493 | 494 | function touchmove( event ) { 495 | 496 | if ( scope.enabled === false ) { return; } 497 | 498 | event.preventDefault(); 499 | event.stopPropagation(); 500 | 501 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 502 | 503 | switch ( event.touches.length ) { 504 | 505 | case 1: // one-fingered touch: rotate 506 | if ( scope.noRotate === true ) { return; } 507 | if ( state !== STATE.TOUCH_ROTATE ) { return; } 508 | 509 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 510 | rotateDelta.subVectors( rotateEnd, rotateStart ); 511 | 512 | // rotating across whole screen goes 360 degrees around 513 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 514 | // rotating up and down along whole screen attempts to go 360, but limited to 180 515 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 516 | 517 | rotateStart.copy( rotateEnd ); 518 | break; 519 | 520 | case 2: // two-fingered touch: dolly 521 | if ( scope.noZoom === true ) { return; } 522 | if ( state !== STATE.TOUCH_DOLLY ) { return; } 523 | 524 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 525 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 526 | var distance = Math.sqrt( dx * dx + dy * dy ); 527 | 528 | dollyEnd.set( 0, distance ); 529 | dollyDelta.subVectors( dollyEnd, dollyStart ); 530 | 531 | if ( dollyDelta.y > 0 ) { 532 | 533 | scope.dollyOut(); 534 | 535 | } else { 536 | 537 | scope.dollyIn(); 538 | 539 | } 540 | 541 | dollyStart.copy( dollyEnd ); 542 | break; 543 | 544 | case 3: // three-fingered touch: pan 545 | if ( scope.noPan === true ) { return; } 546 | if ( state !== STATE.TOUCH_PAN ) { return; } 547 | 548 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 549 | panDelta.subVectors( panEnd, panStart ); 550 | 551 | scope.pan( panDelta ); 552 | 553 | panStart.copy( panEnd ); 554 | break; 555 | 556 | default: 557 | state = STATE.NONE; 558 | 559 | } 560 | 561 | } 562 | 563 | function touchend( /* event */ ) { 564 | 565 | if ( scope.enabled === false ) { return; } 566 | 567 | state = STATE.NONE; 568 | } 569 | 570 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 571 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 572 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 573 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 574 | 575 | this.domElement.addEventListener( 'keydown', onKeyDown, false ); 576 | 577 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 578 | this.domElement.addEventListener( 'touchend', touchend, false ); 579 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 580 | 581 | }; 582 | 583 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); -------------------------------------------------------------------------------- /public/js/lightbox.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lightbox v2.10.0 3 | * by Lokesh Dhakar 4 | * 5 | * More info: 6 | * http://lokeshdhakar.com/projects/lightbox2/ 7 | * 8 | * Copyright 2007, 2018 Lokesh Dhakar 9 | * Released under the MIT license 10 | * https://github.com/lokesh/lightbox2/blob/master/LICENSE 11 | * 12 | * @preserve 13 | */ 14 | 15 | // Uses Node, AMD or browser globals to create a module. 16 | (function (root, factory) { 17 | if (typeof define === 'function' && define.amd) { 18 | // AMD. Register as an anonymous module. 19 | define(['jquery'], factory); 20 | } else if (typeof exports === 'object') { 21 | // Node. Does not work with strict CommonJS, but 22 | // only CommonJS-like environments that support module.exports, 23 | // like Node. 24 | module.exports = factory(require('jquery')); 25 | } else { 26 | // Browser globals (root is window) 27 | root.lightbox = factory(root.jQuery); 28 | } 29 | }(this, function ($) { 30 | 31 | function Lightbox(options) { 32 | this.album = []; 33 | this.currentImageIndex = void 0; 34 | this.init(); 35 | 36 | // options 37 | this.options = $.extend({}, this.constructor.defaults); 38 | this.option(options); 39 | } 40 | 41 | // Descriptions of all options available on the demo site: 42 | // http://lokeshdhakar.com/projects/lightbox2/index.html#options 43 | Lightbox.defaults = { 44 | albumLabel: 'Image %1 of %2', 45 | alwaysShowNavOnTouchDevices: false, 46 | fadeDuration: 600, 47 | fitImagesInViewport: true, 48 | imageFadeDuration: 600, 49 | // maxWidth: 800, 50 | // maxHeight: 600, 51 | positionFromTop: 50, 52 | resizeDuration: 700, 53 | showImageNumberLabel: true, 54 | wrapAround: false, 55 | disableScrolling: false, 56 | /* 57 | Sanitize Title 58 | If the caption data is trusted, for example you are hardcoding it in, then leave this to false. 59 | This will free you to add html tags, such as links, in the caption. 60 | 61 | If the caption data is user submitted or from some other untrusted source, then set this to true 62 | to prevent xss and other injection attacks. 63 | */ 64 | sanitizeTitle: false 65 | }; 66 | 67 | Lightbox.prototype.option = function(options) { 68 | $.extend(this.options, options); 69 | }; 70 | 71 | Lightbox.prototype.imageCountLabel = function(currentImageNum, totalImages) { 72 | return this.options.albumLabel.replace(/%1/g, currentImageNum).replace(/%2/g, totalImages); 73 | }; 74 | 75 | Lightbox.prototype.init = function() { 76 | var self = this; 77 | // Both enable and build methods require the body tag to be in the DOM. 78 | $(document).ready(function() { 79 | self.enable(); 80 | self.build(); 81 | }); 82 | }; 83 | 84 | // Loop through anchors and areamaps looking for either data-lightbox attributes or rel attributes 85 | // that contain 'lightbox'. When these are clicked, start lightbox. 86 | Lightbox.prototype.enable = function() { 87 | var self = this; 88 | //添加touchstart解决ios上面无法现实大图的问题@hillock 89 | $('body').on('click touchstart', 'a[rel^=lightbox], area[rel^=lightbox], a[data-lightbox], area[data-lightbox]', function(event) { 90 | self.start($(event.currentTarget)); 91 | return false; 92 | }); 93 | }; 94 | 95 | // Build html for the lightbox and the overlay. 96 | // Attach event handlers to the new DOM elements. click click click 97 | Lightbox.prototype.build = function() { 98 | if ($('#lightbox').length > 0) { 99 | return; 100 | } 101 | 102 | var self = this; 103 | $('
').appendTo($('body')); 104 | 105 | // Cache jQuery objects 106 | this.$lightbox = $('#lightbox'); 107 | this.$overlay = $('#lightboxOverlay'); 108 | this.$outerContainer = this.$lightbox.find('.lb-outerContainer'); 109 | this.$container = this.$lightbox.find('.lb-container'); 110 | this.$image = this.$lightbox.find('.lb-image'); 111 | this.$nav = this.$lightbox.find('.lb-nav'); 112 | 113 | // Store css values for future lookup 114 | this.containerPadding = { 115 | top: parseInt(this.$container.css('padding-top'), 10), 116 | right: parseInt(this.$container.css('padding-right'), 10), 117 | bottom: parseInt(this.$container.css('padding-bottom'), 10), 118 | left: parseInt(this.$container.css('padding-left'), 10) 119 | }; 120 | 121 | this.imageBorderWidth = { 122 | top: parseInt(this.$image.css('border-top-width'), 10), 123 | right: parseInt(this.$image.css('border-right-width'), 10), 124 | bottom: parseInt(this.$image.css('border-bottom-width'), 10), 125 | left: parseInt(this.$image.css('border-left-width'), 10) 126 | }; 127 | 128 | // Attach event handlers to the newly minted DOM elements 129 | this.$overlay.hide().on('click', function() { 130 | self.end(); 131 | return false; 132 | }); 133 | 134 | this.$lightbox.hide().on('click', function(event) { 135 | if ($(event.target).attr('id') === 'lightbox') { 136 | self.end(); 137 | } 138 | return false; 139 | }); 140 | 141 | this.$outerContainer.on('click', function(event) { 142 | if ($(event.target).attr('id') === 'lightbox') { 143 | self.end(); 144 | } 145 | return false; 146 | }); 147 | 148 | this.$lightbox.find('.lb-prev').on('click', function() { 149 | if (self.currentImageIndex === 0) { 150 | self.changeImage(self.album.length - 1); 151 | } else { 152 | self.changeImage(self.currentImageIndex - 1); 153 | } 154 | return false; 155 | }); 156 | 157 | this.$lightbox.find('.lb-next').on('click', function() { 158 | if (self.currentImageIndex === self.album.length - 1) { 159 | self.changeImage(0); 160 | } else { 161 | self.changeImage(self.currentImageIndex + 1); 162 | } 163 | return false; 164 | }); 165 | 166 | /* 167 | Show context menu for image on right-click 168 | 169 | There is a div containing the navigation that spans the entire image and lives above of it. If 170 | you right-click, you are right clicking this div and not the image. This prevents users from 171 | saving the image or using other context menu actions with the image. 172 | 173 | To fix this, when we detect the right mouse button is pressed down, but not yet clicked, we 174 | set pointer-events to none on the nav div. This is so that the upcoming right-click event on 175 | the next mouseup will bubble down to the image. Once the right-click/contextmenu event occurs 176 | we set the pointer events back to auto for the nav div so it can capture hover and left-click 177 | events as usual. 178 | */ 179 | this.$nav.on('mousedown', function(event) { 180 | if (event.which === 3) { 181 | self.$nav.css('pointer-events', 'none'); 182 | 183 | self.$lightbox.one('contextmenu', function() { 184 | setTimeout(function() { 185 | this.$nav.css('pointer-events', 'auto'); 186 | }.bind(self), 0); 187 | }); 188 | } 189 | }); 190 | 191 | 192 | this.$lightbox.find('.lb-loader, .lb-close').on('click', function() { 193 | self.end(); 194 | return false; 195 | }); 196 | }; 197 | 198 | // Show overlay and lightbox. If the image is part of a set, add siblings to album array. 199 | Lightbox.prototype.start = function($link) { 200 | var self = this; 201 | var $window = $(window); 202 | 203 | $window.on('resize', $.proxy(this.sizeOverlay, this)); 204 | 205 | $('select, object, embed').css({ 206 | visibility: 'hidden' 207 | }); 208 | 209 | this.sizeOverlay(); 210 | 211 | this.album = []; 212 | var imageNumber = 0; 213 | 214 | function addToAlbum($link) { 215 | self.album.push({ 216 | alt: $link.attr('data-alt'), 217 | link: $link.attr('href'), 218 | title: $link.attr('data-title') || $link.attr('title') 219 | }); 220 | } 221 | 222 | // Support both data-lightbox attribute and rel attribute implementations 223 | var dataLightboxValue = $link.attr('data-lightbox'); 224 | var $links; 225 | 226 | if (dataLightboxValue) { 227 | $links = $($link.prop('tagName') + '[data-lightbox="' + dataLightboxValue + '"]'); 228 | for (var i = 0; i < $links.length; i = ++i) { 229 | addToAlbum($($links[i])); 230 | if ($links[i] === $link[0]) { 231 | imageNumber = i; 232 | } 233 | } 234 | } else { 235 | if ($link.attr('rel') === 'lightbox') { 236 | // If image is not part of a set 237 | addToAlbum($link); 238 | } else { 239 | // If image is part of a set 240 | $links = $($link.prop('tagName') + '[rel="' + $link.attr('rel') + '"]'); 241 | for (var j = 0; j < $links.length; j = ++j) { 242 | addToAlbum($($links[j])); 243 | if ($links[j] === $link[0]) { 244 | imageNumber = j; 245 | } 246 | } 247 | } 248 | } 249 | 250 | // Position Lightbox 251 | var top = $window.scrollTop() + this.options.positionFromTop; 252 | var left = $window.scrollLeft(); 253 | this.$lightbox.css({ 254 | top: top + 'px', 255 | left: left + 'px' 256 | }).fadeIn(this.options.fadeDuration); 257 | 258 | // Disable scrolling of the page while open 259 | if (this.options.disableScrolling) { 260 | $('html').addClass('lb-disable-scrolling'); 261 | } 262 | 263 | this.changeImage(imageNumber); 264 | }; 265 | 266 | // Hide most UI elements in preparation for the animated resizing of the lightbox. 267 | Lightbox.prototype.changeImage = function(imageNumber) { 268 | var self = this; 269 | 270 | this.disableKeyboardNav(); 271 | var $image = this.$lightbox.find('.lb-image'); 272 | 273 | this.$overlay.fadeIn(this.options.fadeDuration); 274 | 275 | $('.lb-loader').fadeIn('slow'); 276 | this.$lightbox.find('.lb-image, .lb-nav, .lb-prev, .lb-next, .lb-dataContainer, .lb-numbers, .lb-caption').hide(); 277 | 278 | this.$outerContainer.addClass('animating'); 279 | 280 | // When image to show is preloaded, we send the width and height to sizeContainer() 281 | var preloader = new Image(); 282 | preloader.onload = function() { 283 | var $preloader; 284 | var imageHeight; 285 | var imageWidth; 286 | var maxImageHeight; 287 | var maxImageWidth; 288 | var windowHeight; 289 | var windowWidth; 290 | 291 | $image.attr({ 292 | 'alt': self.album[imageNumber].alt, 293 | 'src': self.album[imageNumber].link 294 | }); 295 | 296 | $preloader = $(preloader); 297 | 298 | $image.width(preloader.width); 299 | $image.height(preloader.height); 300 | 301 | if (self.options.fitImagesInViewport) { 302 | // Fit image inside the viewport. 303 | // Take into account the border around the image and an additional 10px gutter on each side. 304 | 305 | windowWidth = $(window).width(); 306 | windowHeight = $(window).height(); 307 | maxImageWidth = windowWidth - self.containerPadding.left - self.containerPadding.right - self.imageBorderWidth.left - self.imageBorderWidth.right - 20; 308 | maxImageHeight = windowHeight - self.containerPadding.top - self.containerPadding.bottom - self.imageBorderWidth.top - self.imageBorderWidth.bottom - 120; 309 | 310 | // Check if image size is larger then maxWidth|maxHeight in settings 311 | if (self.options.maxWidth && self.options.maxWidth < maxImageWidth) { 312 | maxImageWidth = self.options.maxWidth; 313 | } 314 | if (self.options.maxHeight && self.options.maxHeight < maxImageWidth) { 315 | maxImageHeight = self.options.maxHeight; 316 | } 317 | 318 | // Is the current image's width or height is greater than the maxImageWidth or maxImageHeight 319 | // option than we need to size down while maintaining the aspect ratio. 320 | if ((preloader.width > maxImageWidth) || (preloader.height > maxImageHeight)) { 321 | if ((preloader.width / maxImageWidth) > (preloader.height / maxImageHeight)) { 322 | imageWidth = maxImageWidth; 323 | imageHeight = parseInt(preloader.height / (preloader.width / imageWidth), 10); 324 | $image.width(imageWidth); 325 | $image.height(imageHeight); 326 | } else { 327 | imageHeight = maxImageHeight; 328 | imageWidth = parseInt(preloader.width / (preloader.height / imageHeight), 10); 329 | $image.width(imageWidth); 330 | $image.height(imageHeight); 331 | } 332 | } 333 | } 334 | self.sizeContainer($image.width(), $image.height()); 335 | }; 336 | 337 | preloader.src = this.album[imageNumber].link; 338 | this.currentImageIndex = imageNumber; 339 | }; 340 | 341 | // Stretch overlay to fit the viewport 342 | Lightbox.prototype.sizeOverlay = function() { 343 | this.$overlay 344 | .width($(document).width()) 345 | .height($(document).height()); 346 | }; 347 | 348 | // Animate the size of the lightbox to fit the image we are showing 349 | Lightbox.prototype.sizeContainer = function(imageWidth, imageHeight) { 350 | var self = this; 351 | 352 | var oldWidth = this.$outerContainer.outerWidth(); 353 | var oldHeight = this.$outerContainer.outerHeight(); 354 | var newWidth = imageWidth + this.containerPadding.left + this.containerPadding.right + this.imageBorderWidth.left + this.imageBorderWidth.right; 355 | var newHeight = imageHeight + this.containerPadding.top + this.containerPadding.bottom + this.imageBorderWidth.top + this.imageBorderWidth.bottom; 356 | 357 | function postResize() { 358 | self.$lightbox.find('.lb-dataContainer').width(newWidth); 359 | self.$lightbox.find('.lb-prevLink').height(newHeight); 360 | self.$lightbox.find('.lb-nextLink').height(newHeight); 361 | self.showImage(); 362 | } 363 | 364 | if (oldWidth !== newWidth || oldHeight !== newHeight) { 365 | this.$outerContainer.animate({ 366 | width: newWidth, 367 | height: newHeight 368 | }, this.options.resizeDuration, 'swing', function() { 369 | postResize(); 370 | }); 371 | } else { 372 | postResize(); 373 | } 374 | }; 375 | 376 | // Display the image and its details and begin preload neighboring images. 377 | Lightbox.prototype.showImage = function() { 378 | this.$lightbox.find('.lb-loader').stop(true).hide(); 379 | this.$lightbox.find('.lb-image').fadeIn(this.options.imageFadeDuration); 380 | 381 | this.updateNav(); 382 | this.updateDetails(); 383 | this.preloadNeighboringImages(); 384 | this.enableKeyboardNav(); 385 | }; 386 | 387 | // Display previous and next navigation if appropriate. 388 | Lightbox.prototype.updateNav = function() { 389 | // Check to see if the browser supports touch events. If so, we take the conservative approach 390 | // and assume that mouse hover events are not supported and always show prev/next navigation 391 | // arrows in image sets. 392 | var alwaysShowNav = false; 393 | try { 394 | document.createEvent('TouchEvent'); 395 | alwaysShowNav = (this.options.alwaysShowNavOnTouchDevices) ? true : false; 396 | } catch (e) {} 397 | 398 | this.$lightbox.find('.lb-nav').show(); 399 | 400 | if (this.album.length > 1) { 401 | if (this.options.wrapAround) { 402 | if (alwaysShowNav) { 403 | this.$lightbox.find('.lb-prev, .lb-next').css('opacity', '1'); 404 | } 405 | this.$lightbox.find('.lb-prev, .lb-next').show(); 406 | } else { 407 | if (this.currentImageIndex > 0) { 408 | this.$lightbox.find('.lb-prev').show(); 409 | if (alwaysShowNav) { 410 | this.$lightbox.find('.lb-prev').css('opacity', '1'); 411 | } 412 | } 413 | if (this.currentImageIndex < this.album.length - 1) { 414 | this.$lightbox.find('.lb-next').show(); 415 | if (alwaysShowNav) { 416 | this.$lightbox.find('.lb-next').css('opacity', '1'); 417 | } 418 | } 419 | } 420 | } 421 | }; 422 | 423 | // Display caption, image number, and closing button. 424 | Lightbox.prototype.updateDetails = function() { 425 | var self = this; 426 | 427 | // Enable anchor clicks in the injected caption html. 428 | // Thanks Nate Wright for the fix. @https://github.com/NateWr 429 | if (typeof this.album[this.currentImageIndex].title !== 'undefined' && 430 | this.album[this.currentImageIndex].title !== '') { 431 | var $caption = this.$lightbox.find('.lb-caption'); 432 | if (this.options.sanitizeTitle) { 433 | $caption.text(this.album[this.currentImageIndex].title); 434 | } else { 435 | $caption.html(this.album[this.currentImageIndex].title); 436 | } 437 | $caption.fadeIn('fast') 438 | .find('a').on('click', function(event) { 439 | if ($(this).attr('target') !== undefined) { 440 | window.open($(this).attr('href'), $(this).attr('target')); 441 | } else { 442 | location.href = $(this).attr('href'); 443 | } 444 | }); 445 | } 446 | 447 | if (this.album.length > 1 && this.options.showImageNumberLabel) { 448 | var labelText = this.imageCountLabel(this.currentImageIndex + 1, this.album.length); 449 | this.$lightbox.find('.lb-number').text(labelText).fadeIn('fast'); 450 | } else { 451 | this.$lightbox.find('.lb-number').hide(); 452 | } 453 | 454 | this.$outerContainer.removeClass('animating'); 455 | 456 | this.$lightbox.find('.lb-dataContainer').fadeIn(this.options.resizeDuration, function() { 457 | return self.sizeOverlay(); 458 | }); 459 | }; 460 | 461 | // Preload previous and next images in set. 462 | Lightbox.prototype.preloadNeighboringImages = function() { 463 | if (this.album.length > this.currentImageIndex + 1) { 464 | var preloadNext = new Image(); 465 | preloadNext.src = this.album[this.currentImageIndex + 1].link; 466 | } 467 | if (this.currentImageIndex > 0) { 468 | var preloadPrev = new Image(); 469 | preloadPrev.src = this.album[this.currentImageIndex - 1].link; 470 | } 471 | }; 472 | 473 | Lightbox.prototype.enableKeyboardNav = function() { 474 | $(document).on('keyup.keyboard', $.proxy(this.keyboardAction, this)); 475 | }; 476 | 477 | Lightbox.prototype.disableKeyboardNav = function() { 478 | $(document).off('.keyboard'); 479 | }; 480 | 481 | Lightbox.prototype.keyboardAction = function(event) { 482 | var KEYCODE_ESC = 27; 483 | var KEYCODE_LEFTARROW = 37; 484 | var KEYCODE_RIGHTARROW = 39; 485 | 486 | var keycode = event.keyCode; 487 | var key = String.fromCharCode(keycode).toLowerCase(); 488 | if (keycode === KEYCODE_ESC || key.match(/x|o|c/)) { 489 | this.end(); 490 | } else if (key === 'p' || keycode === KEYCODE_LEFTARROW) { 491 | if (this.currentImageIndex !== 0) { 492 | this.changeImage(this.currentImageIndex - 1); 493 | } else if (this.options.wrapAround && this.album.length > 1) { 494 | this.changeImage(this.album.length - 1); 495 | } 496 | } else if (key === 'n' || keycode === KEYCODE_RIGHTARROW) { 497 | if (this.currentImageIndex !== this.album.length - 1) { 498 | this.changeImage(this.currentImageIndex + 1); 499 | } else if (this.options.wrapAround && this.album.length > 1) { 500 | this.changeImage(0); 501 | } 502 | } 503 | }; 504 | 505 | // Closing time. :-( 506 | Lightbox.prototype.end = function() { 507 | this.disableKeyboardNav(); 508 | $(window).off('resize', this.sizeOverlay); 509 | this.$lightbox.fadeOut(this.options.fadeDuration); 510 | this.$overlay.fadeOut(this.options.fadeDuration); 511 | $('select, object, embed').css({ 512 | visibility: 'visible' 513 | }); 514 | if (this.options.disableScrolling) { 515 | $('html').removeClass('lb-disable-scrolling'); 516 | } 517 | }; 518 | 519 | return new Lightbox(); 520 | })); 521 | --------------------------------------------------------------------------------