web前端模仿微信懸浮窗效果
阿新 • • 發佈:2019-02-19
微信新出了個懸浮窗的功能,因為業務需要,我用js寫了個h5版本的,依賴jq或者zepto,可以自己選擇改造、
請用手機或者電腦瀏覽器模擬手機模式檢視
程式碼如下
<!doctype html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>懸浮窗</title> <script src="https://cdn.staticfile.org/jquery/1.8.3/jquery.min.js"></script> </head> <body> <script> ;(function(doc, win) { var $html = $('html'); resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', recalc = function() { var clientWidth = $(window).width(); if (clientWidth > 640) { clientWidth = 640; } if (!clientWidth) return; var remBase = clientWidth / 16; var fontSize = remBase; while(true){ var actualSize = parseInt( $html.css('font-size') ); $html.attr('style', 'font-size:' + fontSize + 'px'); if (actualSize > remBase && fontSize > 10) { fontSize--; $html.attr('style', 'font-size:' + fontSize + 'px!important'); } else { break; } } }; if (!doc.addEventListener) return; win.addEventListener(resizeEvt, recalc, false); doc.addEventListener('DOMContentLoaded', recalc, false); })(document, window); /** * auth ccy * 微信懸浮窗效果 */ (function (global, factory) { if (typeof define === 'function' && (define.amd || define.cmd)) { define(factory); } else { global.WxLayerBall = factory(); } }(this, function () { 'use strict'; function calcDistance(x1, y1, x2, y2) { return Math.sqrt((x1 - x2)* (x1 - x2) + (y1 - y2)*(y1 - y2)) } var WxLayerBall = function (options) { var defaults = { // 是否自動貼邊 edge: true }; var params = {}; options = options || {}; for (var key in defaults) { if (typeof options[key] !== 'undefined') { params[key] = options[key]; } else { params[key] = defaults[key]; } } var data = { distanceX: 0, distanceY: 0 }; var win = window; // 瀏覽器窗體尺寸 var winWidth = win.innerWidth; var winHeight = win.innerHeight; var $layerDom = $('<div><a href="/" id="__layer-ball" class="__layer-ball">回到首頁</a>\n' + '<div class="__layer-cancel-wrapper">\n' + ' <div class="__layer-outer">\n' + ' </div>\n' + ' <div class="__layer-inner">\n' + ' </div>\n' + ' <div class="__layer-line-left"></div>\n' + ' <div class="__layer-line-right"></div>\n' + ' <p class="__layer-text">取消浮窗</p>\n' + '</div></div>'); var wrapperRight = 6.8 / 16 * winWidth; var wrapperBottom = 6.8 / 16 * winWidth; var $styleSheet = $('<style>.__layer-ball {' + ' position: fixed;' + ' line-height: ' + (0.8 / 16 * winWidth) + 'px;' + ' width: ' + (1.6 / 16 * winWidth) + 'px;' + ' height: ' + (1.6 / 16 * winWidth) + 'px;' + ' padding: ' + (0.2 / 16 * winWidth) + 'px;' + ' text-align: center;' + ' border-radius: 50%;' + ' color: #fff;' + ' font-size: 14px;' + ' z-index: 1001;' + ' background-color: #777779;' + ' background-clip: padding-box;' + ' text-decoration: none;' + ' top: 1em;' + ' }' + ' .__layer-cancel-wrapper{' + ' position: fixed;' + ' width: ' + (13.6 / 16 * winWidth) + 'px;' + ' height: ' + (13.6 / 16 * winWidth) + 'px;' + ' right: -' + wrapperRight + 'px;' + ' bottom: -' + wrapperBottom + 'px;' + ' border-radius: 50%;' + ' display: none;'+ ' z-index: 1000;'+ ' background-color: #eb5352;' + ' font-size: 12px;' + ' box-sizing: content-box;' + ' }' + ' .__layer-outer{' + ' position: absolute;' + ' width: ' + (1.7 / 16 * winWidth) + 'px;' + ' height: ' + (1.7 / 16 * winWidth) + 'px;' + ' left: ' + (3.3 / 16 * winWidth) + 'px;' + ' top: ' + (2.3 / 16 * winWidth) + 'px;' + ' border-radius: 50%;' + ' border: 2px solid #fff;' + ' }' + ' .__layer-inner{' + ' position: absolute;' + ' width: ' + (1.2 / 16 * winWidth) + 'px;' + ' height: ' + (1.2 / 16 * winWidth) + 'px;' + ' left: ' + (3.55 / 16 * winWidth) + 'px;' + ' top: ' + (2.55 / 16 * winWidth) + 'px;' + ' border-radius: 50%;' + ' border: 2px solid #fff;' + ' }' + ' .__layer-line-left{' + ' position: absolute;' + ' height: ' + (2.2 / 16 * winWidth) + 'px;' + ' width: ' + (0.1 / 16 * winWidth) + 'px;' + ' left: ' + (4.1 / 16 * winWidth) + 'px;' + ' top: ' + (2.15 / 16 * winWidth) + 'px;' + ' background-color: #eb5352;' + ' transform: rotate(-45deg);' + ' }' + ' .__layer-line-right{' + ' position: absolute;' + ' height: ' + (2.2 / 16 * winWidth) + 'px;' + ' width: ' + (0.1 / 16 * winWidth) + 'px;' + ' left: ' + (4.2 / 16 * winWidth) + 'px;' + ' top: ' + (2.15 / 16 * winWidth) + 'px;' + ' background-color: #fff;' + ' transform: rotate(-45deg);' + ' }' + ' .__layer-text{' + ' position: absolute;' + ' top: ' + (4.3 / 16 * winWidth) + 'px;' + ' left: ' + (3.2 / 16 * winWidth) + 'px;' + ' color: #ffffff;' + ' opacity: .5;' + ' }</style>'); var $layerWrapper = $layerDom.find('.__layer-cancel-wrapper'); var $layerOuterCircle = $layerDom.find('.__layer-outer'); $('head').append($styleSheet); $('body').append($layerDom); var ele = document.getElementById('__layer-ball'); var ballRemoved = false; // 設定transform座標等方法 var fnTranslate = function (x, y) { x = Math.round(1000 * x) / 1000; y = Math.round(1000 * y) / 1000; ele.style.webkitTransform = 'translate(' + [x + 'px', y + 'px'].join(',') + ')'; ele.style.transform = 'translate3d(' + [x + 'px', y + 'px', 0].join(',') + ')'; }; var strStoreDistance = ''; if (ele.id && win.localStorage && (strStoreDistance = localStorage['WxLayerBall_' + ele.id])) { var arrStoreDistance = strStoreDistance.split(','); ele.distanceX = +arrStoreDistance[0]; ele.distanceY = +arrStoreDistance[1]; fnTranslate(ele.distanceX, ele.distanceY); } // 顯示拖拽元素 ele.style.visibility = 'visible'; // 如果元素在螢幕之外,位置使用初始值 var initBound = ele.getBoundingClientRect(); if (initBound.left < -0.5 * initBound.width || initBound.top < -0.5 * initBound.height || initBound.right > winWidth + 0.5 * initBound.width || initBound.bottom > winHeight + 0.5 * initBound.height ) { ele.distanceX = 0; ele.distanceY = 0; fnTranslate(0, 0); } ele.addEventListener('touchstart', function (event) { if (data.inertiaing) { return; } $layerWrapper.slideDown(); var events = event.touches[0] || event; data.posX = events.pageX; data.posY = events.pageY; data.touching = true; if (ele.distanceX) { data.distanceX = ele.distanceX; } if (ele.distanceY) { data.distanceY = ele.distanceY; } // 元素的位置資料 data.bound = ele.getBoundingClientRect(); data.timerready = true; }); // easeOutBounce演算法 /* * t: current time(當前時間); * b: beginning value(初始值); * c: change in value(變化量); * d: duration(持續時間)。 **/ var easeOutBounce = function (t, b, c, d) { if ((t /= d) < (1 / 2.75)) { return c * (7.5625 * t * t) + b; } else if (t < (2 / 2.75)) { return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; } else if (t < (2.5 / 2.75)) { return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; } else { return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; } }; document.addEventListener('touchmove', function (event) { if (data.touching !== true) { return; } // 當移動開始的時候開始記錄時間 if (data.timerready == true) { data.timerstart = +new Date(); data.timerready = false; } event.preventDefault(); var events = event.touches[0] || event; data.nowX = events.pageX; data.nowY = events.pageY; var distanceX = data.nowX - data.posX, distanceY = data.nowY - data.posY; // 此時元素的位置 var absLeft = data.bound.left + distanceX, absTop = data.bound.top + distanceY, absRight = absLeft + data.bound.width, absBottom = absTop + data.bound.height; // console.log(absRight, absBottom); var distance = calcDistance(absLeft,absTop,winWidth,winHeight); if(distance < wrapperBottom){ $layerOuterCircle.css({transform:"scale(1.1)"}) $layerWrapper.css({'border':"4px solid #eb5352"}) ballRemoved = true }else{ $layerOuterCircle.css({transform:"scale(1)"}) $layerWrapper.css({border:"none"}) ballRemoved = false } // 邊緣檢測 if (absLeft < 0) { distanceX = distanceX - absLeft; } if (absTop < 0) { distanceY = distanceY - absTop; } if (absRight > winWidth) { distanceX = distanceX - (absRight - winWidth); } if (absBottom > winHeight) { distanceY = distanceY - (absBottom - winHeight); } // 元素位置跟隨 var x = data.distanceX + distanceX, y = data.distanceY + distanceY; fnTranslate(x, y); // 快取移動位置 ele.distanceX = x; ele.distanceY = y; }, { // fix #3 #5 passive: false }); document.addEventListener('touchend', function () { if (data.touching === false) { // fix iOS fixed bug return; } data.touching = false; // 計算速度 data.timerend = +new Date(); if (!data.nowX || !data.nowY) { return; } // 移動的水平和垂直距離 var distanceX = data.nowX - data.posX, distanceY = data.nowY - data.posY; if (Math.abs(distanceX) < 5 && Math.abs(distanceY) < 5) { return; } // 開始慣性緩動 data.inertiaing = true; var edge = function () { // 時間 var start = 0, during = 25; // 初始值和變化量 var init = ele.distanceX, y = ele.distanceY, change = 0; // 判斷元素現在在哪個半區 var bound = ele.getBoundingClientRect(); if (bound.left + bound.width / 2 < winWidth / 2) { change = -1 * bound.left; } else { change = winWidth - bound.right; } var run = function () { // 如果使用者觸控元素,停止繼續動畫 if (data.touching == true) { data.inertiaing = false; return; } start++; var x = easeOutBounce(start, init, change, during); fnTranslate(x, y); if (start < during) { requestAnimationFrame(run); } else { ele.distanceX = x; ele.distanceY = y; data.inertiaing = false; if (win.localStorage && !ballRemoved) { localStorage['WxLayerBall_' + ele.id] = [x, y].join(); } } }; run(); }; if (params.edge) { edge(); } if(ballRemoved){ $(ele).remove() } $layerWrapper.slideUp() }); }; new WxLayerBall(); })); </script> </body> </html>