├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── MIT-LICENSE.txt ├── README.md ├── composer.json ├── start.php ├── start_for_win.bat ├── start_io.php ├── start_web.php └── web ├── icon-close.png ├── index.html ├── main.css ├── notify.js └── repeat.jpg /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | *.js linguist-language=PHP 3 | *.css linguist-language=PHP 4 | *.html linguist-language=PHP 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | open_collective: walkor 4 | patreon: walkor 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .project 3 | .settings/org.eclipse.php.core.prefs -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | web-msg-sender 2 | ============== 3 | 4 | Web消息实时推送,支持在线用户数实时统计。基于[PHPSocket.IO](https://github.com/walkor/phpsocket.io)开发,使用websocket推送数据,当浏览器不支持websocket时自动切换comet推送数据。 5 | 6 | 效果截图 7 | ====== 8 | ![web-msg-sender-demo](http://www.workerman.net/img/web-msg-sender-demo.png) 9 | 10 | 线上demo 11 | ====== 12 | 13 | 接收消息页面:[http://www.workerman.net:2123/](http://www.workerman.net:2123/) 14 | 后端推送接口url:[http://www.workerman.net:2121/?type=publish&to=&content=msgcontent](http://www.workerman.net:2121/?type=publish&to=&content=msgcontent) 15 | to为接收消息的uid,如果不传递则向所有人推送消息 16 | content 为消息内容 17 | 18 | 注:可以通过php或者其它语言的curl功能实现后台推送 19 | 20 | 下载安装 21 | ====== 22 | 1、git clone https://github.com/walkor/web-msg-sender 23 | 24 | 2、composer install 25 | 26 | 后端服务启动停止 27 | ====== 28 | ## Linux系统 29 | ### 启动服务 30 | php start.php start -d 31 | ### 停止服务 32 | php start.php stop 33 | ### 服务状态 34 | php start.php status 35 | 36 | ## windows系统 37 | 双击start_for_win.bat 38 | 39 | 如果启动不成功请参考 [Workerman手册](http://doc3.workerman.net/install/requirement.html) 配置环境 40 | 41 | 前端代码类似: 42 | ==== 43 | ```javascript 44 | // 引入前端文件 45 | 46 | 56 | ``` 57 | 58 | 后端调用api向任意用户推送数据 59 | ==== 60 | ```php 61 | 'publish', 68 | 'content' => '这个是推送的测试数据', 69 | 'to' => $to_uid, 70 | ); 71 | $ch = curl_init (); 72 | curl_setopt ( $ch, CURLOPT_URL, $push_api_url ); 73 | curl_setopt ( $ch, CURLOPT_POST, 1 ); 74 | curl_setopt ( $ch, CURLOPT_HEADER, 0 ); 75 | curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 ); 76 | curl_setopt ( $ch, CURLOPT_POSTFIELDS, $post_data ); 77 | curl_setopt ( $ch, CURLOPT_HTTPHEADER, array("Expect:")); 78 | $return = curl_exec ( $ch ); 79 | curl_close ( $ch ); 80 | var_export($return); 81 | ``` 82 | 83 | 常见问题: 84 | ==== 85 | 如果通信不成功检查防火墙 86 | /sbin/iptables -I INPUT -p tcp --dport 2120 -j ACCEPT 87 | /sbin/iptables -I INPUT -p tcp --dport 2121 -j ACCEPT 88 | /sbin/iptables -I INPUT -p tcp --dport 2123 -j ACCEPT 89 | 90 | 91 | workerman相关参见 [www.workerman.net](http://www.workerman.net/) 92 | ================= 93 | 94 | workerman更多有趣的应用: 95 | ======================= 96 | 97 | [小蝌蚪聊天室](http://kedou.workerman.net) 98 | 99 | [多人在线flappy birds](http://www.workerman.net/demos/flappy-bird/) 100 | 101 | [其它](http://www.workerman.net/applications) 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "workerman/web-msg-sender", 3 | "type" : "project", 4 | "keywords": ["socket.io"], 5 | "homepage": "http://www.workerman.net", 6 | "license" : "MIT", 7 | "require": { 8 | "workerman/phpsocket.io" : ">1.0.0" 9 | }, 10 | "autoload": { 11 | "psr-4": { 12 | "" : "./" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /start.php: -------------------------------------------------------------------------------- 1 | on('connection', function($socket){ 22 | // 当客户端发来登录事件时触发 23 | $socket->on('login', function ($uid)use($socket){ 24 | global $uidConnectionMap, $last_online_count, $last_online_page_count; 25 | // 已经登录过了 26 | if(isset($socket->uid)){ 27 | return; 28 | } 29 | // 更新对应uid的在线数据 30 | $uid = (string)$uid; 31 | if(!isset($uidConnectionMap[$uid])) 32 | { 33 | $uidConnectionMap[$uid] = 0; 34 | } 35 | // 这个uid有++$uidConnectionMap[$uid]个socket连接 36 | ++$uidConnectionMap[$uid]; 37 | // 将这个连接加入到uid分组,方便针对uid推送数据 38 | $socket->join($uid); 39 | $socket->uid = $uid; 40 | // 更新这个socket对应页面的在线数据 41 | $socket->emit('update_online_count', "当前{$last_online_count}人在线,共打开{$last_online_page_count}个页面"); 42 | }); 43 | 44 | // 当客户端断开连接是触发(一般是关闭网页或者跳转刷新导致) 45 | $socket->on('disconnect', function () use($socket) { 46 | if(!isset($socket->uid)) 47 | { 48 | return; 49 | } 50 | global $uidConnectionMap, $sender_io; 51 | // 将uid的在线socket数减一 52 | if(--$uidConnectionMap[$socket->uid] <= 0) 53 | { 54 | unset($uidConnectionMap[$socket->uid]); 55 | } 56 | }); 57 | }); 58 | 59 | // 当$sender_io启动后监听一个http端口,通过这个端口可以给任意uid或者所有uid推送数据 60 | $sender_io->on('workerStart', function(){ 61 | // 监听一个http端口 62 | $inner_http_worker = new Worker('http://0.0.0.0:2121'); 63 | // 当http客户端发来数据时触发 64 | $inner_http_worker->onMessage = function(TcpConnection $http_connection, Request $request){ 65 | global $uidConnectionMap; 66 | $post = $request->post(); 67 | $post = $post ? $post : $request->get(); 68 | // 推送数据的url格式 type=publish&to=uid&content=xxxx 69 | switch(@$post['type']){ 70 | case 'publish': 71 | global $sender_io; 72 | $to = @$post['to']; 73 | $post['content'] = htmlspecialchars(@$post['content']); 74 | // 有指定uid则向uid所在socket组发送数据 75 | if($to){ 76 | $sender_io->to($to)->emit('new_msg', $post['content']); 77 | // 否则向所有uid推送数据 78 | }else{ 79 | $sender_io->emit('new_msg', @$post['content']); 80 | } 81 | // http接口返回,如果用户离线socket返回fail 82 | if($to && !isset($uidConnectionMap[$to])){ 83 | return $http_connection->send('offline'); 84 | }else{ 85 | return $http_connection->send('ok'); 86 | } 87 | } 88 | return $http_connection->send('fail'); 89 | }; 90 | // 执行监听 91 | $inner_http_worker->listen(); 92 | 93 | // 一个定时器,定时向所有uid推送当前uid在线数及在线页面数 94 | Timer::add(1, function(){ 95 | global $uidConnectionMap, $sender_io, $last_online_count, $last_online_page_count; 96 | $online_count_now = count($uidConnectionMap); 97 | $online_page_count_now = array_sum($uidConnectionMap); 98 | // 只有在客户端在线数变化了才广播,减少不必要的客户端通讯 99 | if($last_online_count != $online_count_now || $last_online_page_count != $online_page_count_now) 100 | { 101 | $sender_io->emit('update_online_count', "当前{$online_count_now}人在线,共打开{$online_page_count_now}个页面"); 102 | $last_online_count = $online_count_now; 103 | $last_online_page_count = $online_page_count_now; 104 | } 105 | }); 106 | }); 107 | 108 | if(!defined('GLOBAL_START')) 109 | { 110 | Worker::runAll(); 111 | } 112 | -------------------------------------------------------------------------------- /start_web.php: -------------------------------------------------------------------------------- 1 | name = 'web'; 13 | 14 | define('WEBROOT', __DIR__ . DIRECTORY_SEPARATOR . 'web'); 15 | 16 | $web->onMessage = function (TcpConnection $connection, Request $request) { 17 | $path = $request->path(); 18 | if ($path === '/') { 19 | $connection->send(exec_php_file(WEBROOT.'/index.html')); 20 | return; 21 | } 22 | $file = realpath(WEBROOT. $path); 23 | if (false === $file) { 24 | $connection->send(new Response(404, array(), '

404 Not Found

')); 25 | return; 26 | } 27 | // Security check! Very important!!! 28 | if (strpos($file, WEBROOT) !== 0) { 29 | $connection->send(new Response(400)); 30 | return; 31 | } 32 | if (\pathinfo($file, PATHINFO_EXTENSION) === 'php') { 33 | $connection->send(exec_php_file($file)); 34 | return; 35 | } 36 | 37 | $if_modified_since = $request->header('if-modified-since'); 38 | if (!empty($if_modified_since)) { 39 | // Check 304. 40 | $info = \stat($file); 41 | $modified_time = $info ? \date('D, d M Y H:i:s', $info['mtime']) . ' ' . \date_default_timezone_get() : ''; 42 | if ($modified_time === $if_modified_since) { 43 | $connection->send(new Response(304)); 44 | return; 45 | } 46 | } 47 | $connection->send((new Response())->withFile($file)); 48 | }; 49 | 50 | function exec_php_file($file) { 51 | \ob_start(); 52 | // Try to include php file. 53 | try { 54 | include $file; 55 | } catch (\Exception $e) { 56 | echo $e; 57 | } 58 | return \ob_get_clean(); 59 | } 60 | 61 | if(!defined('GLOBAL_START')) 62 | { 63 | Worker::runAll(); 64 | } 65 | -------------------------------------------------------------------------------- /web/icon-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkor/web-msg-sender/a29cef7bef28ac9909d9822381b379c391a9aea8/web/icon-close.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

14 | 15 |
16 |
17 |
18 |

介绍:

19 | Web-msg-sender 是一个web消息推送系统,基于PHPSocket.IO开发。


20 |

支持以下特性:

21 |
    22 |
  • 多浏览器支持
  • 23 |
  • 支持针对单个用户推送消息
  • 24 |
  • 支持向所有用户推送消息
  • 25 |
  • 长连接推送(websocket或者comet),消息即时到达
  • 26 |
  • 支持在线用户数实时统计推送(见页脚统计)
  • 27 |
  • 支持在线页面数实时统计推送(见页脚统计)
  • 28 |
29 |

测试:

30 | 当前用户uid:
31 | 可以通过url:http://:2121?type=publish&to=&content=消息内容 向当前用户发送消息
32 | 可以通过url:http://:2121?type=publish&to=&content=消息内容 向所有在线用户推送消息
33 | 42 |
43 | 44 | 63 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /web/main.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | body { 3 | margin:0px; padding:0px; 4 | font-family: Arial, Helvetica, sans-serif; 5 | background:url(/repeat.jpg); 6 | font-size:15px; 7 | color:#000; 8 | } 9 | ul{list-style:none; margin:0px; padding:0px; margin-top:20px;} 10 | li{padding-bottom:20px;} 11 | .sticky p, .floated p, .fixed p, .ondemand p{ float:left; padding:0px; margin:0px; margin-left:10px; line-height:45px; color:#fff; font-size:12px;} 12 | .sticky a, .floated a, .fixed a, .ondemand a{ float:right; margin:13px 10px 0px 0px; } 13 | img{border:0px;} 14 | .wrapper{padding:20px;} 15 | 16 | .sticky { 17 | 18 | position:fixed; 19 | top:0; 20 | left:0; 21 | z-index:1000; 22 | width:100%; 23 | border-bottom:3px solid #fff !important; 24 | 25 | background: #91BD09; /* Old browsers */ 26 | background: -moz-linear-gradient(top, #91BD09 0%, #91BD09 100%); /* FF3.6+ */ 27 | 28 | /* FireFox 3.6 */ 29 | /* Safari4+, Chrome */ 30 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#91BD09', endColorstr='#91BD09')"; 31 | -pie-background: linear-gradient(#91BD09, #91BD09 100%); 32 | behavior: url(PIE.htc); 33 | -moz-box-shadow: 1px 1px 7px #676767; 34 | -webkit-box-shadow: 1px 1px 7px #676767; 35 | box-shadow: 1px 1px 7px #676767; 36 | height: 45px; 37 | background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #91BD09),color-stop(1, #91BD09));/* IE6,IE7 */ 38 | /* IE8 */ 39 | /* Firefox F3.5+ */ 40 | /* Safari3.0+, Chrome */ 41 | } 42 | 43 | 44 | .floated { 45 | 46 | position:absolute; 47 | top:0; 48 | left:0; 49 | z-index:1000; 50 | width:100%; 51 | border-bottom:3px solid #fff !important; 52 | background: #0e59ae; /* Old browsers */ 53 | background: -moz-linear-gradient(top, #0e59ae 0%, #0e59ae 100%); /* FF3.6+ */ 54 | 55 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#0E59AE', endColorstr='#0E59AE')"; 56 | 57 | -moz-box-shadow: 1px 1px 7px #676767; 58 | -webkit-box-shadow: 1px 1px 7px #676767; 59 | box-shadow: 1px 1px 7px #676767; 60 | height: 45px; 61 | background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #0E59AE),color-stop(1, #0E59AE));/* IE6,IE7 */ 62 | -pie-background: linear-gradient(#0E59AE, #0E59AE 100%); 63 | behavior: url(PIE.htc); 64 | } 65 | 66 | 67 | .fixed { 68 | position:absolute; 69 | top:0; 70 | left:0; 71 | width:100%; 72 | border-bottom:3px solid #fff !important; 73 | 74 | background: #660099; /* Old browsers */ 75 | background: -moz-linear-gradient(top, #660099 0%, #660099 100%); /* FF3.6+ */ 76 | 77 | /* FireFox 3.6 */ 78 | /* Safari4+, Chrome */ 79 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#660099', endColorstr='#660099')"; 80 | -pie-background: linear-gradient(#660099, #660099 100%); 81 | behavior: url(PIE.htc); 82 | -moz-box-shadow: 1px 1px 7px #676767; 83 | -webkit-box-shadow: 1px 1px 7px #676767; 84 | box-shadow: 1px 1px 7px #676767; 85 | height: 45px; 86 | background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #660099),color-stop(1, #660099));/* IE6,IE7 */ 87 | /* IE8 */ 88 | /* Firefox F3.5+ */ 89 | /* Safari3.0+, Chrome */ 90 | } 91 | 92 | .ondemand { 93 | 94 | width:100%; 95 | border-bottom:3px solid #fff !important; 96 | position:absolute; 97 | top:0; 98 | left:0; 99 | z-index:1000; 100 | 101 | background: #CC0000; /* Old browsers */ 102 | background: -moz-linear-gradient(top, #CC0000 0%, #CC0000 100%); /* FF3.6+ */ 103 | 104 | /* FireFox 3.6 */ 105 | /* Safari4+, Chrome */ 106 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#CC0000', endColorstr='#CC0000')"; 107 | -pie-background: linear-gradient(#CC0000, #CC0000 100%); 108 | behavior: url(PIE.htc); 109 | -moz-box-shadow: 1px 1px 7px #676767; 110 | -webkit-box-shadow: 1px 1px 7px #676767; 111 | box-shadow: 1px 1px 7px #676767; 112 | height: 45px; 113 | background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #CC0000),color-stop(1, #CC0000));/* IE6,IE7 */ 114 | /* IE8 */ 115 | /* Firefox F3.5+ */ 116 | /* Safari3.0+, Chrome */ 117 | } 118 | .ondemand-button 119 | { 120 | width:40px !important; 121 | height:40px; 122 | float:right !important; 123 | z-index:999; 124 | position:absolute; 125 | margin-right:100px!important; 126 | } 127 | 128 | 129 | 130 | 131 | 132 | #footer{width:100%; margin:0 auto; font-size:12px; color:#0E59AE; height:30px; margin-top:200px;border-top:1px solid #CCC;padding:18px;} 133 | .hide{display:none;} 134 | 135 | 136 | /* Buttons */ 137 | 138 | .round.button { 139 | -moz-border-radius: 15px; 140 | -webkit-border-radius: 15px; 141 | border-radius: 15px; 142 | background-image: url(button-images/round-button-overlay.png); 143 | border: 1px solid rgba(0, 0, 0, 0.25); 144 | font-size: 13px; 145 | padding: 0; 146 | } 147 | 148 | .button { 149 | -moz-border-radius: 5px; 150 | -webkit-border-radius: 5px; 151 | border-radius: 5px; 152 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.50); 153 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.50); 154 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.50); 155 | background: #222; 156 | border: 1px solid rgba(0, 0, 0, 0.25); 157 | color: white !important; 158 | cursor: pointer; 159 | display: inline-block; 160 | font-size: 13px; 161 | font-weight: bold; 162 | line-height: 1; 163 | overflow: visible; 164 | padding: 5px 15px 6px; 165 | position: relative; 166 | text-decoration: none; 167 | text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); 168 | width: auto; 169 | text-align: center; 170 | } 171 | 172 | .round.button span { 173 | -moz-border-radius: 14px; 174 | -webkit-border-radius: 14px; 175 | border-radius: 14px; 176 | display: block; 177 | line-height: 1; 178 | padding: 4px 15px 6px; 179 | } 180 | 181 | 182 | .green.button { 183 | background-color:#91BD09; 184 | } 185 | .green.button:hover { 186 | background-color:#749A02; 187 | } 188 | .green.button:active { 189 | background-color:#a4d50b; 190 | } 191 | .blue.button { 192 | background-color:#0E59AE; 193 | } 194 | .blue.button:hover { 195 | background-color:#063468; 196 | } 197 | .blue.button:active { 198 | background-color:#1169cc; 199 | } 200 | .purple.button { 201 | background-color:#660099; 202 | } 203 | .purple.button:hover { 204 | background-color:#330066; 205 | } 206 | .purple.button:active { 207 | background-color:#7f02bd; 208 | } 209 | 210 | .red.button { 211 | background-color:#CC0000; 212 | } 213 | .red.button:hover { 214 | background-color:#990000; 215 | } 216 | .red.button:active { 217 | background-color:#ea0202; 218 | } 219 | .close 220 | {} 221 | 222 | .show{ 223 | 224 | background: #CC0000; /* Old browsers */ 225 | background: -moz-linear-gradient(top, #CC0000 0%, #CC0000 100%); /* FF3.6+ */ 226 | 227 | 228 | /* FireFox 3.6 */ 229 | /* Safari4+, Chrome */ 230 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#CC0000', endColorstr='#CC0000')"; 231 | -pie-background: linear-gradient(#CC0000, #CC0000 100%); 232 | behavior: url(PIE.htc); 233 | -moz-box-shadow: 1px 1px 7px #676767; 234 | -webkit-box-shadow: 1px 1px 7px #676767; 235 | box-shadow: 1px 1px 7px #676767; 236 | height: 35px; 237 | float: right; 238 | width: 30px; 239 | overflow:hidden; 240 | /*margin-top: 0px !important;*/ 241 | margin-right: 10px !important; 242 | text-align: center; 243 | background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #CC0000),color-stop(1, #CC0000));/* IE6,IE7 */ 244 | /* IE8 */ 245 | /* Firefox F3.5+ */ 246 | /* Safari3.0+, Chrome */ 247 | /* Opera 10.5, IE 9.0 */ 248 | 249 | 250 | } 251 | 252 | .show img{margin-top:10px;} 253 | -------------------------------------------------------------------------------- /web/notify.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $.fn.extend({ 3 | notify: function (options) { 4 | var settings = $.extend({ type: 'sticky', speed: 500, onDemandButtonHeight: 35 }, options); 5 | return this.each(function () { 6 | var wrapper = $(this); 7 | var ondemandBtn = $('.ondemand-button'); 8 | var dh = -35; 9 | var w = wrapper.outerWidth() - ondemandBtn.outerWidth(); 10 | ondemandBtn.css('left', w).css('margin-top', dh + "px" ); 11 | var h = -wrapper.outerHeight(); 12 | wrapper.addClass(settings.type).css('margin-top', h).addClass('visible').removeClass('hide'); 13 | if (settings.type != 'ondemand') { 14 | wrapper.stop(true, false).animate({ marginTop: 0 }, settings.speed); 15 | } 16 | else { 17 | ondemandBtn.stop(true, false).animate({ marginTop: 0 }, settings.speed); 18 | } 19 | 20 | var closeBtn = $('.close', wrapper); 21 | closeBtn.click(function () { 22 | if (settings.type == 'ondemand') { 23 | wrapper.stop(true, false).animate({ marginTop: h }, settings.speed, function () { 24 | wrapper.removeClass('visible').addClass('hide'); 25 | ondemandBtn.stop(true, false).animate({ marginTop: 0 }, settings.speed); 26 | }); 27 | } 28 | else { 29 | wrapper.stop(true, false).animate({ marginTop: h }, settings.speed, function () { 30 | wrapper.removeClass('visible').addClass('hide'); 31 | }); 32 | } 33 | }); 34 | if (settings.type == 'floated') { 35 | $(document).scroll(function (e) { 36 | wrapper.stop(true, false).animate({ top: $(document).scrollTop() }, settings.speed); 37 | }).resize(function (e) { 38 | wrapper.stop(true, false).animate({ top: $(document).scrollTop() }, settings.speed); 39 | }); 40 | } 41 | else if (settings.type == 'ondemand') { 42 | ondemandBtn.click(function () { 43 | $(this).animate({ marginTop: dh }, settings.speed, function () { 44 | wrapper.removeClass('hide').addClass('visible').animate({ marginTop: 0 }, settings.speed, function () { 45 | 46 | }); 47 | }) 48 | }); 49 | } 50 | 51 | }); 52 | 53 | } 54 | }); 55 | })(jQuery); 56 | -------------------------------------------------------------------------------- /web/repeat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkor/web-msg-sender/a29cef7bef28ac9909d9822381b379c391a9aea8/web/repeat.jpg --------------------------------------------------------------------------------