Files
Pandora/Pandora.js
2025-08-13 20:23:06 +08:00

3228 lines
107 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;(w => {
'use strict'
const OSSBase64 = require('./src/base64.js')
const icoConfig = require('./src/icoConfig.json')
// 缺省字段
const Alphabet = ['active', 'local', 'localhost', '', '127.0.0.1', '192.168', 'inherit', '提示', '错误', '警告']
// 弹框集合
w.pdDialogs = []
// 兼容处理&&基础方法
//requestAnimationFrame
if (!w.requestAnimationFrame) {
let lastTime, reqId
w.requestAnimationFrame = callback => {
const currTime = new Date().getTime(),
timeToCall = Math.max(0, 16 - (currTime - lastTime)),
reqId = setTimeout(() => {
callback(currTime + timeToCall)
}, timeToCall)
lastTime = currTime + timeToCall
return reqId
}
w.cancelAnimationFrame = reqId => {
clearTimeout(reqId)
}
}
;(function () {
//获取CSS变量
const getRoot = name => {
return w.getComputedStyle(document.documentElement).getPropertyValue(`--${name}`)
}
// 添加alert、confirm皮肤样式
let rootText = ''
if (!getRoot('alertTheme')) rootText += `/*alert背景*/--alertTheme:${Alphabet[6]};`
if (!getRoot('alertBg')) rootText += `/*alert遮罩*/--alertBg:${Alphabet[6]};`
if (!getRoot('alertFontSize')) rootText += '/*alert字体大小*/--alertFontSize:1rem;'
if (!getRoot('alertColor')) rootText += '/*alert字体颜色*/--alertColor:#000;'
if (!getRoot('confirmTheme')) rootText += `/*confirm背景*/--confirmTheme:#fff;`
if (!getRoot('confirmBg')) rootText += `/*confirm遮罩*/--confirmBg:${Alphabet[6]};`
if (!getRoot('confirmBtnBg')) rootText += '/*confirm按钮背景*/--confirmBtnBg:#fafafa;'
if (!getRoot('confirmFontSize')) rootText += '/*confirm字体大小*/--confirmFontSize:1rem;'
if (!getRoot('confirmColor')) rootText += '/*confirm字体颜色*/--confirmColor:#636363;'
if (!getRoot('confirmBtnColor')) rootText += '/*confirm按钮字体颜色*/--confirmBtnColor:#636363;'
if (!getRoot('confirmBtnBorder')) rootText += '/*confirm按钮边框颜色*/--confirmBtnBorder:#f1f1f1;'
const style = document.createElement('style')
style.innerText = `:root{${rootText}}`
document.querySelector('head').appendChild(style)
//是否已经显示遮罩
let isMaskShow = !1
const setGlobalCSS = (mask, maskBg, blur, plan) => {
mask.style.cssText = `
position:fixed;
inset:0;
top:0;
left:0;
right:0;
bottom:0;
z-index:999999999;
width:100%;
height:100%;
display:flex;
justify-content:center;
background:${Alphabet[6]};
background:${maskBg}`
mask.className = 'Pd_Mask'
blur.style.cssText = plan.style.cssText = `position:absolute;inset:0;top:0;left:0;right:0;bottom:0;`
plan.style.cssText += `background:${Alphabet[6]};filter:blur(10px) saturate(2)`
if (maskBg) plan.style.cssText += `background:rgba(255,255,255,.66)`
}
//修改原生alert
if (w.resetAlert == undefined) {
w.resetAlert = !0
}
if (w.resetAlert) {
w.alert = (content, speed = 800) => {
let timeout,
mask = document.createElement(`div`),
maskBg = !isMaskShow ? getRoot(`alertBg`) : null,
div = document.createElement(`div`),
msg = document.createElement(`p`),
blur = document.createElement(`div`),
plan = document.createElement(`div`),
Theme = getRoot(`alertTheme`),
fontSize = getRoot(`alertFontSize`),
color = getRoot(`alertColor`)
setGlobalCSS(mask, maskBg, blur, plan)
mask.style.cssText += `align-items:flex-end`
div.style.cssText = `
background:${Alphabet[6]};
background:${Theme};
text-align:center;
color:${color};
font-size:${fontSize};
padding:.5em 1.5em;
line-height:1.3;
transition:opacity .4s ease-out;
margin-bottom:5vh;
box-shadow:0 8px 16px rgba(0,0,0,.25);
border-radius:6px;
position:relative;
overflow:hidden`
div.id = 'Pd_alert'
msg.style.cssText = `margin:0;position:relative`
msg.innerText = content ? content + '' : ''
div.appendChild(blur)
div.appendChild(plan)
div.appendChild(msg)
mask.appendChild(div)
document.body.appendChild(mask)
mask.onclick = () => {
clearTimeout(timeout)
document.body.removeChild(mask)
mask = div = timeout = color = null
}
clearTimeout(timeout)
timeout = setTimeout(() => {
div.style.opacity = 0
div.addEventListener('transitionend', () => {
document.body.removeChild(mask)
mask = div = timeout = color = null
})
}, speed)
}
}
//修改原生confirm
if (w.resetConfirm == undefined) {
w.resetConfirm = !0
}
if (w.resetConfirm) {
w.confirm = config => {
let title = config.title || '',
content,
confirmText,
cancelText,
mask = document.createElement(`div`),
div = document.createElement(`div`),
blur = document.createElement(`div`),
plan = document.createElement(`div`),
titleCon = document.createElement(`h2`),
msg = document.createElement(`p`),
confirm = document.createElement(`button`),
cancel = document.createElement(`button`),
maskBg = !isMaskShow ? getRoot(`confirmBg`) : null,
btnBg = getRoot(`confirmBtnBg`),
Theme = getRoot(`confirmTheme`),
fontSize = getRoot(`confirmFontSize`),
color = getRoot(`confirmColor`),
btnColor = getRoot(`confirmBtnColor`),
btnBorder = getRoot(`confirmBtnBorder`)
const showConfirm = config.showConfirm == undefined ? !0 : config.showConfirm,
showCancel = config.showCancel == undefined ? !0 : config.showCancel
confirmText = config.confirmText ? config.confirmText.toString() : '确认'
cancelText = config.cancelText ? config.cancelText.toString() : '取消'
setGlobalCSS(mask, maskBg, blur, plan)
mask.style.cssText += 'align-items:center;'
div.style.cssText = `
background:${Alphabet[6]};
background:${Theme};
text-align:center;
color:${color};
font-size:${fontSize};
max-width:75vw;
min-width:20em;
box-shadow:0px 35px 35px -10px rgba(0,0,0,.33);
border-radius:10px;
position:relative;
white-space: break-spaces;
word-break: break-all;
overflow:hidden`
div.id = 'Pd_confirm'
titleCon.style.cssText = `position:relative;font-size:1.3em;min-height:1em;background:${btnBg};color:${btnColor};margin:0;padding:.5em 0`
msg.style.cssText = `position:relative;border-top:1px solid ${btnBorder};width:100%;max-height:70vh;border-bottom:1px solid ${btnBorder};margin:0 auto;box-sizing:border-box;padding:2em 10%;line-height:1.4;overflow:auto`
const buttonCSS = `position: relative;width:${
showConfirm && showCancel ? '50%' : '100%'
};font-size:1em;appearance:none;background:${btnBg};color:${btnColor};border:none;border-right:1px solid ${btnBorder};padding:1em 0;cursor:pointer;outline:none`
confirm.style.cssText = cancel.style.cssText = buttonCSS
const removeConfirm = () => {
document.body.removeChild(mask)
isMaskShow = !1
}
if (!config.content) {
content = config + ''
} else {
content = config.content.toString()
}
titleCon.innerText = title ? title.toString() : ''
msg.innerText = content ? content.toString() : ''
div.appendChild(blur)
div.appendChild(plan)
div.appendChild(titleCon)
div.appendChild(msg)
if (showConfirm) {
confirm.innerText = confirmText
div.appendChild(confirm)
}
if (showCancel) {
cancel.innerText = cancelText
div.appendChild(cancel)
}
mask.appendChild(div)
document.body.appendChild(mask)
isMaskShow = !0
return new Promise((ok, no) => {
if (showConfirm) {
confirm.onclick = () => {
removeConfirm()
ok()
}
}
if (showCancel) {
cancel.onclick = () => {
removeConfirm()
no()
}
}
})
}
}
//loading遮罩
const LoadingName = 'Pd_loader'
let mask
w.showLoading = (progress = null) => {
if (document.getElementById(LoadingName)) {
document.getElementById(LoadingName) && document.body.removeChild(document.getElementById(LoadingName))
}
mask = document.createElement('div')
const svg = new Image(),
em = document.createElement('em')
svg.src = icoConfig.load
mask.id = LoadingName
mask.style.cssText = 'font-size:1rem;width:100%;height:100%;position:fixed;z-index:999999999;inset:0;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.65);display:flex;align-items:center;justify-content:center;flex-direction:column'
svg.style.width = svg.style.height = '3em'
mask.appendChild(svg)
if (progress !== null) {
em.style.cssText = 'font-style:normal;font-size:1em;color:#fff;margin-top:.5em;'
em.innerText = progress
mask.appendChild(em)
}
document.body.appendChild(mask)
}
w.hideLoading = () => {
if (document.getElementById(LoadingName)) {
document.body.removeChild(mask)
}
}
})()
//内置方法
class PandoraEX {
constructor(input = null) {
this.getInput = obj => {
if (Array.isArray(obj)) return obj
if (['[object Window]', '[object HTMLDocument]'].includes(obj + '')) return w
if (document.querySelectorAll(obj)) {
if (document.querySelectorAll(obj).length > 1) {
return document.querySelectorAll(obj)
} else {
if (obj) {
return document.querySelector(obj)
} else {
return null
}
}
} else {
return console.error(`[${Alphabet[8]}] 未找到 ${obj}`)
}
}
this.get = this.getInput(input)
this.getLength = () => {
if (this.get) {
const { length } = this.get
if (length) {
return length
} else {
return 1
}
}
}
this.length = this.getLength()
this.guid = () => {
const S4 = () => {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
}
return `PandoraEX_${S4()}${S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`
}
//生成GUID
this.pid = this.guid()
//默认参数赋值
this.extend = (config, options) => {
if (!options) {
options = config
} else {
Object.keys(config).forEach(e => {
if (typeof options[e] === undefined || typeof options[e] === undefined + '') options[e] = config[e]
})
}
return options
}
//选择指定下标元素
this.eq = index => {
try {
if (this.getInput(input).length) {
this.get = this.getInput(input)[index]
} else {
this.get = this.getInput(input)
}
} catch (err) {
console.error(`[${Alphabet[8]}] 未找到该下标`, err)
}
return this
}
//选择子级元素
this.child = name => {
const ele = this.get
try {
if (ele.querySelectorAll(name).length > 1) {
this.get = ele.querySelectorAll(name)
} else {
this.get = ele.querySelectorAll(name)[0]
}
} catch (err) {
console.error(`[${Alphabet[8]}] 未找到该子级`, err)
}
return this
}
//查找子级元素
this.find = name => {
const ele = this.get
try {
this.get = ele.querySelectorAll(name)
} catch (err) {
console.error(`[${Alphabet[8]}] 未找到该子级`, err)
}
return this
}
//选择父级
this.parent = () => {
const ele = this.get
try {
this.get = ele.parentElement
} catch (err) {
console.error(`[${Alphabet[8]}] 未找到该父级`, err)
}
return this
}
//选择其他同级元素
this.siblings = name => {
const ele = this.get,
parent = this.parent()
let siblingArr = []
for (let e of parent.child(name).get) {
if (ele != e) siblingArr.push(e)
}
this.get = siblingArr
return this
}
//选择上一个同级元素
this.prev = () => {
const ele = this.get
this.get = ele.previousElementSibling
return this
}
//选择下一个同级元素
this.next = () => {
const ele = this.get
this.get = ele.nextElementSibling
return this
}
//选择第一个元素
this.first = () => {
return this.eq(0)
}
//选择最后一个元素
this.last = () => {
const ele = this.get
if (ele.length) {
return this.eq(ele.length - 1)
} else {
return this.first()
}
}
//遍历元素集
this.each = fn => {
const ele = this.get
if (ele.length) {
for (let i = 0; i < ele.length; i++) {
fn && fn(this.eq(i), i)
}
} else {
fn && fn(this.first(), 0)
}
return this
}
//获取或修改样式
this.css = name => {
const ele = this.get
let style = []
style = name
if (style) {
if (typeof style === 'string') {
if (ele.length) {
return w.getComputedStyle(ele[0]).getPropertyValue(style)
} else {
return w.getComputedStyle(ele).getPropertyValue(style)
}
} else {
if (ele.length) {
for (let the of ele) {
Object.keys(style).forEach(e => {
the.style[e] = style[e]
})
}
} else {
Object.keys(style).forEach(e => {
ele.style[e] = style[e]
})
}
}
} else {
return w.getComputedStyle(ele).getPropertyValue('*')
}
}
//获取布局信息
this.offset = () => {
const ele = this.get
if (ele.length) {
return ele[0].getBoundingClientRect()
} else {
return ele.getBoundingClientRect()
}
}
//获取或设置宽度
this.width = (width = null) => {
const ele = this.get
if (width) {
if (ele.length) {
for (let the of ele) the.style.width = width
} else {
ele.style.width = width
}
} else {
if (ele.length) {
return ele[0].offsetWidth
} else {
return ele.offsetWidth
}
}
}
//获取或设置高度
this.height = (height = null) => {
const ele = this.get
if (height) {
if (ele.length) {
for (let the of ele) the.style.height = height
} else {
ele.style.height = height
}
} else {
if (ele.length) {
return ele[0].offsetHeight
} else {
return ele.offsetHeight
}
}
}
//获取或插入文本
this.text = str => {
const ele = this.get
if (str != undefined) {
if (ele.length) {
for (let the of ele) the.innerText = str + ''
} else {
ele.innerText = str + ''
}
} else {
if (ele.length) {
return ele[0].innerText
} else {
return ele.innerText
}
}
return this
}
//获取或插入HTML
this.html = content => {
const ele = this.get
if (content) {
this.empty()
if (ele.length) {
for (let the of ele) the.innerHTML = content
} else {
ele.innerHTML = content
}
} else {
if (ele.length) {
return ele[0].innerHTML
} else {
return ele.innerHTML
}
}
return this
}
//获取或插入值
this.val = value => {
const ele = this.get
if (ele.nodeName.toLowerCase() == 'select') {
if (value != null && value != undefined) {
for (let the of ele) the.options[the.selectedIndex].value = value
} else {
return ele.options[ele.selectedIndex].value
}
} else {
if (ele.length) {
if (value != null && value != undefined) {
for (let the of ele) the.value = value
} else {
return ele[0].value
}
} else {
if (value != null && value != undefined) {
ele.value = value
} else {
return ele.value
}
}
}
return this
}
//插入元素
this.prepend = target => {
const ele = this.get
if (ele.length > 1) {
if (ele.nodeName.toLowerCase() == 'select') {
if (typeof target == 'object') {
ele.insertBefore(target, ele.firstChild)
} else {
const div = document.createElement('div')
div.innerHTML = target
for (const dom of div.childNodes) ele.insertBefore(dom, ele.firstChild)
}
} else {
for (let the of ele) {
if (typeof target == 'object') {
the.insertBefore(target, the.firstChild)
} else {
const div = document.createElement('div')
div.innerHTML = target
for (const dom of div.childNodes) the.insertBefore(dom, the.firstChild)
}
}
}
} else {
if (typeof target == 'object') {
ele.insertBefore(target, ele.firstChild)
} else {
const div = document.createElement('div')
div.innerHTML = target
for (const dom of div.childNodes) ele.insertBefore(dom, ele.firstChild)
}
}
return this
}
this.append = target => {
const ele = this.get
if (ele.length > 1) {
if (ele.nodeName.toLowerCase() == 'select') {
if (typeof target == 'object') {
ele.appendChild(target)
} else {
const template = document.createElement('template')
template.innerHTML = target
ele.appendChild(document.importNode(template.content, !0))
}
} else {
for (let the of ele) {
if (typeof target == 'object') {
the.appendChild(target)
} else {
const template = document.createElement('template')
template.innerHTML = target
the.appendChild(document.importNode(template.content, !0))
}
}
}
} else {
if (typeof target == 'object') {
ele.appendChild(target)
} else {
const template = document.createElement('template')
template.innerHTML = target
ele.appendChild(document.importNode(template.content, !0))
}
}
return this
}
//清空容器
this.empty = () => {
const ele = this.get
if (ele.length) {
for (let the of ele) {
while (the.firstChild) {
the.removeChild(the.firstChild)
}
}
} else {
while (ele.firstChild) {
ele.removeChild(ele.firstChild)
}
}
return this
}
//移除元素
this.remove = () => {
const ele = this.get
if (ele.length) {
for (let the of ele) {
try {
the.parentElement.removeChild(the)
} catch (err) {
console.error(`[${Alphabet[8]}] 未找到元素`, err)
}
}
} else {
try {
ele.parentElement.removeChild(ele)
} catch (err) {
console.error(`[${Alphabet[8]}] 未找到元素`, err)
}
}
return this
}
//添加class
this.addClass = name => {
const ele = this.get,
addThat = the => {
const beforeClass = the.classList.value
if (beforeClass) {
if (beforeClass.indexOf(name) < 0) the.className = `${beforeClass} ${name.trim()}`
} else {
the.className = name.trim()
}
}
if (ele.length) {
for (let the of ele) addThat(the)
} else {
addThat(ele)
}
return this
}
//移除class
this.removeClass = name => {
const ele = this.get,
removeThat = the => {
if (the.classList.value) {
let beforeClass = the.classList.value.split(' '),
afterClass
beforeClass.map((cur, idx) => {
if (cur === name) beforeClass.splice(idx, 1)
})
afterClass = beforeClass.join(' ')
the.className = afterClass
}
}
if (ele.length) {
for (let the of ele) removeThat(the)
} else {
removeThat(ele)
}
return this
}
//是否拥有class名
this.hasClass = name => {
const ele = this.get,
classlist = ele.classList.value.indexOf(' ') > -1 ? ele.classList.value.split(' ') : ele.classList.value
if (classlist.indexOf(name) > -1) {
return !0
} else {
return !1
}
}
//添加属性
this.attr = (inject, val) => {
const ele = this.get
if (ele.length) {
if (typeof inject == 'object') {
for (let the of ele) {
for (let keyName in inject) the.setAttribute(keyName, inject[keyName])
}
return this
} else {
if (val) {
for (let the of ele) the.setAttribute(inject, val)
return this
} else {
return ele[0].getAttribute(inject)
}
}
} else {
if (typeof inject == 'object') {
for (let keyName in inject) ele.setAttribute(keyName, inject[keyName])
return this
} else {
if (val) {
ele.setAttribute(inject, val)
return this
} else {
return ele.getAttribute(inject)
}
}
}
}
//移除属性
this.removeAttr = name => {
const ele = this.get
if (ele.length) {
for (let the of ele) the.removeAttribute(name)
} else {
ele.removeAttribute(name)
}
return this
}
//绑定事件
this.bind = (eventName, fn, options = {}, bool = !1) => {
const ele = this.get
const capture = options.capture || !1,
once = options.once || !1,
passive = options.passive || !1
if (ele.length) {
let eleIndex = 0
for (let the of ele) {
the.addEventListener(eventName, fn, { capture, once, passive }, bool)
the.index = eleIndex++
the.eventList = []
the.eventList.push({ name: eventName, callback: fn })
}
} else {
ele.addEventListener(eventName, fn, { capture, once, passive }, bool)
ele.eventList = []
ele.eventList.push({ name: eventName, callback: fn })
}
return this
}
//解绑事件
this.unbind = eventName => {
const ele = this.get
if (ele.length) {
for (let the of ele) {
if (the.eventList) {
the.eventList.map((e, i) => {
if (e.name === eventName) {
the.removeEventListener(eventName, e.callback)
the.eventList.splice(i, 1)
}
})
}
}
} else {
if (ele.eventList) {
ele.eventList.map((e, i) => {
if (e.name === eventName) {
ele.removeEventListener(eventName, e.callback)
ele.eventList.splice(i, 1)
}
})
}
}
return this
}
//获取焦点
this.focus = () => {
const ele = this.get
if (ele.length) {
for (let the of ele) {
the.focus()
}
} else {
ele.focus()
}
return this
}
//移除焦点
this.blur = () => {
const ele = this.get
if (ele.length) {
for (let the of ele) {
the.blur()
}
} else {
ele.blur()
}
return this
}
//点击&触摸点击事件
this.click = fn => {
const ele = this.get
if (ele.length) {
for (let a = 0; a < ele.length; a++) {
if (w.ontouchstart) {
ele[a].ontouchstart = () => {
this.get = ele[a]
fn(this, a)
}
} else {
ele[a].onclick = () => {
this.get = ele[a]
fn(this, a)
}
}
}
} else {
if (w.ontouchstart) {
ele.ontouchstart = () => {
fn(this, null)
}
} else {
ele.onclick = () => {
fn(this, null)
}
}
}
return this
}
//双击事件
this.dblclick = fn => {
const ele = this.get
if (ele.length) {
for (let a = 0; a < ele.length; a++) {
ele[a].ondblclick = () => {
this.get = ele[a]
fn(this, a)
}
}
} else {
ele.ondblclick = () => {
fn(this, null)
}
}
return this
}
//主动触发事件
this.trigger = eventName => {
const ele = this.get
const event = new Event(eventName)
if (ele.length) {
for (let the of ele) {
the.dispatchEvent(event)
}
} else {
ele.dispatchEvent(event)
}
return this
}
//长按事件
this.taping = (fn, cb) => {
const ele = this.get
let infiniteFrame
const infiniteFn = () => {
fn && fn(ele)
infiniteFrame = requestAnimationFrame(infiniteFn)
}
if (w.ontouchstart) {
ele.ontouchstart = event => {
event.preventDefault()
cancelAnimationFrame(infiniteFn)
infiniteFn()
}
ele.ontouchend = () => {
cb && cb(ele)
cancelAnimationFrame(infiniteFrame)
}
} else {
ele.onmousedown = () => {
cancelAnimationFrame(infiniteFn)
infiniteFn()
}
ele.onmouseup = () => {
cb && cb(ele)
cancelAnimationFrame(infiniteFrame)
}
}
return this
}
//显示
this.show = callback => {
this.attr(`beforeHide`) ? this.css({ display: this.attr(`beforeHide`) }) : this.css({ display: 'block' })
callback && setTimeout(callback)
return this
}
//隐藏
this.hide = callback => {
if (!this.attr(`beforeHide`)) this.attr('beforeHide', this.css(`display`) == 'none' ? 'block' : this.css(`display`))
this.css({ display: 'none' })
callback && setTimeout(callback)
return this
}
// 淡入
this.fadeIn = (speed = 'fast', callback) => {
const that = this
let opacity = 0,
req
const fade = () => {
if (opacity < 100) {
switch (speed) {
case 'fast':
opacity += 5
break
case 'slow':
opacity++
break
default:
opacity += speed
break
}
req = requestAnimationFrame(fade)
} else {
callback && callback()
cancelAnimationFrame(req)
}
that.css({ opacity: opacity / 100 })
}
that.show(() => {
that.css({ opacity: 0 })
fade()
})
return this
}
// 淡出
this.fadeOut = (speed = 'fast', callback) => {
const that = this
let opacity = 100,
req
const fade = () => {
if (opacity > 0) {
switch (speed) {
case 'fast':
opacity -= 5
break
case 'slow':
opacity--
break
default:
opacity -= speed
break
}
req = requestAnimationFrame(fade)
} else {
that.hide(() => {
callback && callback()
cancelAnimationFrame(req)
})
}
that.css({ opacity: opacity / 100 })
}
fade()
return this
}
//ajax
this.ajax = options => {
let config = {
//接口地址(类型:字符串)
url: null,
//请求类型(类型字符串可选参数post、get)
type: 'get',
//是否同步请求(类型:布尔)
async: !1,
//设置请求头(类型:对象)
headers: { 'Content-type': 'application/x-www-form-urlencoded' },
//发送数据类型(类型字符串可选参数json、form)
dataType: 'json',
//返回体类型(类型:字符串)
responseType: 'json',
//发送数据(类型json或form格式必须和发送数据类型保持一致)
data: null,
//加载中回调方法(类型:方法)
progress: null,
//成功回调方法(类型:方法)
success: null,
//失败回调方法(类型:方法)
error: null,
}
config = this.extend(config, options)
const http = new XMLHttpRequest(),
{ url, type, async, headers, dataType, data, progress, success, error, responseType } = config
let params
if (dataType == 'json') {
if (data)
params = Object.keys(data)
.map(key => {
return `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`
})
.join(`&`)
} else {
params = data
}
if (async) {
http.responseType = responseType
}
try {
// 监听加载中
http.upload.onprogress = event => {
if (event.lengthComputable) {
progress && progress(Math.floor((event.loaded / event.total) * 100))
}
}
} catch (e) {}
// 向全局添加取消请求方法
w.cancelAjax = () => {
http.abort()
}
return new Promise((resolve, reject) => {
http.onreadystatechange = () => {
if (http.status === 200 && http.readyState === 4 && http.response) {
if (async) {
resolve(http.response)
success && success(http.response)
} else {
resolve(JSON.parse(http.response))
success && success(JSON.parse(http.response))
}
}
}
http.onerror = err => {
reject(err)
error && error(err)
}
http.open(type.toUpperCase(), url, async)
if (headers) {
// 如果data是formdata类型不设置请求头
if (data && data.constructor !== FormData) {
for (let keys in headers) http.setRequestHeader(keys, headers[keys])
}
}
http.send(params)
})
}
//fetch
this.fetch = options => {
let config = {
//接口地址(类型:字符串)
url: null,
//设置请求头(类型:对象)
headers: { 'Content-type': 'application/x-www-form-urlencoded' },
//请求类型(类型字符串可选参数post、get)
type: 'get',
//发送数据(类型JSON)
data: null,
//返回数据格式化(类型:方法)
returnData(res) {
return res.json()
},
}
config = this.extend(config, options)
const { url, data, headers, type, returnData } = config
return new Promise((resolve, reject) => {
if (data) {
fetch(url, { body: JSON.stringify(data), headers, method: type.toLocaleUpperCase() })
.then(res => {
if (res.ok) {
return returnData(res)
} else {
console.error(`[${Alphabet[8]}] 请求错误`, res)
}
})
.then(resolve)
.catch(reject)
} else {
fetch(url, { headers, method: type.toLocaleUpperCase() })
.then(res => {
if (res.ok) {
return returnData(res)
} else {
console.error(`[${Alphabet[8]}] 请求错误`, res)
}
})
.then(resolve)
.catch(reject)
}
})
}
//表单序列化
this.serialize = () => {
const ele = this.get
let obj = {}
for (let e of ele.querySelectorAll(`*`)) {
if (e.getAttribute(`name`)) {
const keyName = e.getAttribute(`name`)
if (keyName) {
switch (e.type) {
case 'radio':
if (e.checked) obj[keyName] = e.value
break
case 'checkbox':
if (e.checked) {
obj[keyName] = e.value
} else {
obj[keyName] = !1
}
break
default:
obj[keyName] = e.value
break
}
}
}
}
return obj
}
//NEW设置表单
this.setForm = (obj = null) => {
if (obj) {
const ele = this.get,
formChilds = ele.querySelectorAll(`[name]`)
formChilds.forEach(e => {
const keyName = e.getAttribute(`name`)
if (obj[keyName]) {
if (e.readOnly) return console.error(`[${Alphabet[8]}] 该元素为只读,无法设置值`)
switch (e.type) {
case 'radio':
if (e.value == obj[keyName]) {
e.checked = !0
} else {
e.checked = !1
}
break
case 'checkbox':
if (obj[keyName]) e.checked = !0
break
case 'file':
e.files = obj[keyName]
break
case 'select':
e.value = obj[keyName]
break
default:
e.value = obj[keyName]
break
}
}
})
} else {
console.error(`[${Alphabet[8]}] 无可设置的表单数据`)
}
return this
}
//渲染变量
this.globalData = {}
//设置渲染变量
this.setData = obj => {
return new Promise((success, fail) => {
for (let key in obj) {
try {
this.globalData[key] = obj[key]
} catch (err) {
console.error(`[${Alphabet[8]} - Mush] 变量修改失败`, err)
fail(err)
}
}
success()
})
}
//获取渲染变量
this.getData = key => {
if (this.globalData[key]) {
return this.globalData[key]
} else {
console.error(`[${Alphabet[8]} - Mush] 获取的变量不存在!`)
return null
}
}
//模板渲染
this.template = (route, container) => {
return new Promise((success, fail) => {
const temp = (() => {
let cur
const template = document.querySelectorAll(`template`)
for (let a = 0; a < template.length; a++) {
if (template[a].getAttribute(`route`) == route) cur = template[a]
}
return cur
})()
if (temp) {
this.empty()
let url = temp.getAttribute('src')
const that = this
if (url) {
that
.fetch({
url,
headers: { 'Content-type': 'text/html' },
returnData(res) {
return res.text()
},
})
.then(res => {
const node = document.createElement(`template`)
node.innerHTML = res
if (node.content.querySelectorAll(`link`)) {
let linkArr = []
for (let link of node.content.querySelectorAll(`link`)) {
linkArr.push(
that.fetch({
url: link.href,
headers: { 'Content-type': 'text/html' },
returnData(res) {
return res.text()
},
})
)
node.content.removeChild(link)
}
Promise.all(linkArr).then(res => {
res.map(the => {
const style = document.createElement('style')
style.innerHTML = the
node.content.appendChild(style)
})
insertHTML()
})
} else {
insertHTML()
}
//插入HTML
function insertHTML() {
container.appendChild(document.importNode(node.content, !0))
for (let script of node.content.querySelectorAll(`script`)) {
if (script.getAttribute('src')) {
that
.fetch({
url: script.src,
headers: { 'Content-type': 'text/html' },
returnData(res) {
return res.text()
},
})
.then(res => {
eval(res)
})
} else {
eval(script.innerHTML)
}
}
success()
}
})
.catch(err => {
console.error(`[${Alphabet[8]} - Router] 不存在以下路由:${err.target.responseURL}`)
fail(`${route}`)
})
} else {
container.appendChild(document.importNode(temp.content, !0))
success()
}
} else {
console.error(`[${Alphabet[8]} - Router] 不存在以下路由:${route}`)
fail(`${route}`)
}
})
}
//获取url参数并转换成对象
this.getParams = () => {
const url = location.href.split(`?`)
if (location.href.indexOf(`?`) > -1) {
let obj = {}
if (url[1].split(`&`)) {
const params = url[1].split(`&`)
params.map(v => {
obj[v.split(`=`)[0]] = v.split(`=`)[1]
})
} else {
obj[url[1].split(`=`)[0]] = obj[url[1].split(`=`)[1]]
}
return obj
} else {
return null
}
}
// HASH改变
this.hashChange = (callback, routes) => {
const getRoutePath = () => {
if (location.hash.indexOf(`#`) > -1) {
return location.hash.match(/#(\S*)\?/) === null ? location.hash.match(/#(\S*)/).input.replace(`#`, ``) : location.hash.match(/#(\S*)\?/).input.replace(`#`, ``)
} else {
return !1
}
}
const routePath = getRoutePath() || routes[0].path
callback(routePath)
}
// 数组相关方法
this.Array = {
// 原始数组
originals: this.get,
// 随机打乱数组
Random() {
let originals = this.originals
for (let i = 0; i < originals.length; i++) originals[i] = originals[i]
originals.sort(() => {
return 0.5 - Math.random()
})
return originals
},
// 判断是否存在重复
hasRepeat() {
const originals = this.originals
let hash = {}
for (let i in originals) {
if (hash[originals[i]]) {
return !0
}
hash[originals[i]] = !0
}
return !1
},
// 数组求和
Sum() {
const arr = this.originals
let s = 0
for (let i = arr.length - 1; i >= 0; i--) s += arr[i]
return s
},
}
}
}
//拓展方法
const PandoraJs = SuperClass => {
return class extends SuperClass {
constructor(obj) {
super(obj)
}
//Mustache渲染
Mush(options) {
let config = {
//渲染数据(类型:对象)
data: null,
//生命周期-首次渲染完成(类型:方法;返回初始渲染数据)
Init: null,
// 生命周期-每次更新渲染完成(类型:方法;返回最新渲染数据)
Update: null,
}
config = this.extend(config, options)
let Html = this.html(),
bHtml = Html,
matchValue
const that = this,
{ data, Init, Update } = config,
pattern = new RegExp('{{.*?}}', 'g'),
patterns = new RegExp('{{.*?}}')
// 重构渲染数据
const getObj = res => {
let newObj = {}
for (let keyName of Object.keys(res)) newObj[keyName] = res[keyName]
return newObj
}
// 获取所有MUSH变量
const getMush = () => {
let r = []
Html.match(pattern).forEach((e, index) => {
r[index] = e.split(`{{`)[1].split(`}}`)[0]
})
return r
}
matchValue = getMush()
//渲染html
const renderHtml = () => {
return new Promise(next => {
Html = bHtml
for (let value of matchValue) {
for (let keyName in data) {
if (value === keyName) {
Html = Html.replace(patterns, data[value] + '' || '')
}
}
}
that.html(Html)
next()
})
}
// 返回所有唯一变量
const unique = array => {
let r = []
for (let i = 0, l = array.length; i < l; i++) {
for (let j = i + 1; j < l; j++) {
if (array[i] == array[j]) {
j == ++i
}
}
r.push(array[i])
}
return r
}
//遍历变量是否被动态修改
unique(matchValue).forEach(e => {
Object.defineProperty(that.globalData, e, {
set(value) {
data[e] = value
renderHtml()
.then(() => {
Update && Update(getObj(that.globalData))
})
.catch(err => {
console.error(`[${Alphabet[8]} - Mush] 变量更新失败`, err)
})
},
get() {
return data[e]
},
enumerable: !0,
})
})
renderHtml()
.then(() => {
Init && Init(getObj(that.globalData))
})
.catch(err => {
console.error(`[${Alphabet[8]} - Mush] 初始渲染失败`, err)
})
return this
}
// TODO 静态路由 二级目录DOM虚拟化完善生命周期
Router(options) {
let config = {
// 路由路径集合(类型:数组)
routes: null,
}
config = this.extend(config, options)
const { routes } = config
// 把路由路径集合中的path放入数组
let routePath = []
for (let route of routes) routePath.push(route.path)
// 渲染路由
const render = path => {
// 创建影子节点
const shadow = document.createElement('div')
// 当前路由集合
const { component, callback } = routes[routePath.indexOf(path)]
shadow.attachShadow({ mode: 'open', serializable: true })
// 读取页面
fetch(component)
.then(res => {
return res.text()
})
.then(html => {
shadow.innerHTML = html
this.empty()
// 把影子节点转换为真实节点,并插入到容器中
this.append(shadow.getHTML())
// 覆写A标签
const a = document.querySelectorAll('a')
for (let e of a) {
e.onclick = e => {
const href = e.target.getAttribute('href')
if (href) {
if (!/http|https|ftp|ftps|mailto|javascript/.test(href) && href.indexOf('#') < 0) {
// 阻止默认事件
e.preventDefault()
this.to(href)
}
}
}
}
callback && callback(w.history.state)
})
}
// 路由切换
this.to = path => {
// 判断路由是否带有参数
let query = null
if (path.indexOf('?') > -1) {
query = path.split('?')[1]
path = path.split('?')[0]
// 把参数转换为对象
let obj = {}
query.split('&').map(e => {
obj[e.split('=')[0]] = e.split('=')[1]
})
query = obj
}
if (routePath.includes(path)) {
if (query) {
// 把参数转换为字符串
const queryStr = Object.keys(query).map(key => {
return `${key}=${query[key]}`
})
w.history.replaceState(query, null, `${path}?${queryStr.join('&')}`)
} else {
w.history.pushState(query, null, path)
}
render(path)
} else {
console.error(`[${Alphabet[8]} - Router] ${path} 路由不存在!`)
}
}
this.to(w.location.href.split(w.location.origin)[1])
return this
}
//轮播切换
Switcher(options) {
let config = {
//过渡速度/秒(类型:数字)
Speed: 1,
//动画曲线(类型字符串参考css3动画曲线)
Curve: 'ease',
//切换效果(类型字符串可选参数slider、fade)
Effect: 'slider',
//方向(类型字符串可选参数horizontal、vertical)
Direction: 'horizontal',
//惯性回弹(类型:布尔)
Inertia: !0,
//滑动比例(类型:数字)
Distance: 3,
//自动切换间隔/秒(类型数字为0时不自动切换)
AutoSpeed: 0,
//分页器(类型:布尔)
Pagination: !1,
//悬浮停止(类型:布尔)
Hover: !1,
//滚轮滚动(类型:布尔)
Scroll: !1,
//初始坐标(类型:数字)
InitPage: 0,
//循环(类型:布尔)
Loop: !1,
//切换状态变化(类型:方法)
onChange: null,
//是否窗口大小改变时自动调整(类型:布尔)
AutoResize: !1,
}
config = this.extend(config, options)
const { Speed, Curve, Effect, Direction, Inertia, Distance, AutoSpeed, Pagination, Hover, Scroll, InitPage, Loop, onChange, AutoResize } = config,
childEle = this.get,
parentEle = childEle[0].parentElement,
total = childEle.length,
transitionend = () => {
if (isScrolling) {
isScrolling = !1
parentEle.removeEventListener('transitionend', transitionend)
}
}
let currentPage = InitPage,
childW = childEle[0].offsetWidth,
childH = childEle[0].offsetHeight,
AutoTimeout,
isScrolling = !1,
isFristTime = !0
//切换
const Swiper = (moveTo = null) => {
if (typeof moveTo == 'number') currentPage = moveTo
Pager(currentPage)
if (!isFristTime) {
onChange && onChange(currentPage)
}
switch (Effect) {
case 'fade':
for (let cur of childEle) {
if (cur.className.indexOf('active') > -1) {
cur.style.opacity = 1
cur.style.zIndex = 2
} else {
cur.style.opacity = 0
cur.style.zIndex = 1
}
}
break
default:
switch (Direction) {
case 'vertical':
parentEle.style.transform = `translate3d(0,${-1 * (childH * currentPage)}px,0)`
break
case 'horizontal':
default:
parentEle.style.transform = `translate3d(${-1 * (childW * currentPage)}px,0,0)`
break
}
break
}
if (Loop) {
parentEle.addEventListener('transitionend', transitionend)
} else {
if (currentPage === 0 || currentPage === total - 1) {
transitionend()
} else {
parentEle.addEventListener('transitionend', transitionend)
}
}
}
//分页器
const Pager = current => {
for (let e of childEle) e.className = e.className.replace(Alphabet[0], '').trim()
if (childEle[currentPage].className) {
childEle[currentPage].className += ` ${Alphabet[0]}`
} else {
childEle[currentPage].className += Alphabet[0]
}
if (Pagination) {
parentEle.parentElement.querySelector(`.Pd-pagination`) && parentEle.parentElement.removeChild(parentEle.parentElement.querySelector(`.Pd-pagination`))
const pager = document.createElement('div')
pager.className = 'Pd-pagination'
for (let a = 0; a < total; a++) {
const pageChild = document.createElement('a'),
textNode = childEle[a].getAttribute(`data-title`) ? document.createTextNode(childEle[a].getAttribute(`data-title`)) : document.createTextNode(a)
pageChild.setAttribute('href', 'javascript:void 0')
if (a === current) pageChild.className = Alphabet[0]
pageChild.appendChild(textNode)
pager.appendChild(pageChild)
}
parentEle.parentElement.insertBefore(pager, parentEle.nextElementSibling)
for (let a = 0; a < parentEle.parentElement.querySelectorAll(`.Pd-pagination a`).length; a++) {
const e = parentEle.parentElement.querySelectorAll(`.Pd-pagination a`)[a],
idx = a
e.onclick = () => {
currentPage = idx
Swiper()
}
}
}
}
//上一个
const Prev = () => {
isFristTime = !1
if (currentPage < total && currentPage > 0) {
currentPage--
} else if (currentPage === 0 && Loop) {
currentPage = total - 1
} else {
isScrolling = !1
}
Swiper()
}
//下一个
const Next = () => {
isFristTime = !1
if (currentPage < total - 1) {
currentPage++
} else if (currentPage === total - 1 && Loop) {
currentPage = 0
} else {
currentPage = total - 1
}
Swiper()
}
let startX, startY, endX, endY, curX, curY
//方法:滑动开始
const touchStart = event => {
clearTimeout(AutoTimeout)
cancelAnimationFrame(AutoPlayFrame)
const { pageX, pageY } = event.changedTouches[0]
const { left, top } = parentEle.parentElement.getBoundingClientRect()
switch (config.Direction) {
case 'vertical':
startY = pageY - top
break
case 'horizontal':
default:
startX = pageX - left
break
}
parentEle.style.transition = null
}
//方法:滑动中
const touchMove = event => {
const { pageX, pageY } = event.changedTouches[0],
{ left, top } = parentEle.parentElement.getBoundingClientRect()
curX = pageX - left
curY = pageY - top
switch (Effect) {
case 'fade':
for (let cur of childEle) cur.style.transition = `opacity ${Speed}s linear`
break
default:
switch (Direction) {
case 'vertical':
if (startY > curY) {
if (currentPage != total - 1) parentEle.style.transform = `translate3d(0,${-1 * (startY - curY + childH * currentPage)}px,0)`
} else {
if (currentPage != 0) parentEle.style.transform = `translate3d(0,${-1 * (childH * currentPage) + Math.abs(curY - startY)}px,0)`
}
break
case 'horizontal':
default:
if (startX > curX) {
parentEle.style.transform = `translate3d(${-1 * (startX - curX + childW * currentPage)}px,0,0)`
} else {
parentEle.style.transform = `translate3d(${-1 * (childW * currentPage) + Math.abs(curX - startX)}px,0,0)`
}
break
}
break
}
}
//方法:滑动结束
const touchEnd = event => {
clearTimeout(AutoTimeout)
AutoPlay()
parentEle.style.transition = `transform ${Speed}s ${Curve}`
const { pageX, pageY } = event.changedTouches[0],
{ left, top } = parentEle.parentElement.getBoundingClientRect()
switch (Direction) {
case 'vertical':
endY = pageY - top
switch (Effect) {
case 'fade':
if (startY - endY > childH / config.Distance && currentPage === total - 1) {
currentPage = 0
} else if (startY - endY > childH / config.Distance && currentPage < total - 1) {
Next()
} else if (endY - startY > childH / config.Distance) {
Prev()
}
for (let cur of childEle) {
cur.style.transition = `opacity ${config.Speed}s ${config.Curve}`
cur.style.opacity = 0
cur.style.zIndex = 1
}
childEle[currentPage].style.opacity = 1
childEle[currentPage].style.zIndex = 2
Swiper()
break
default:
if (startY - endY > childH / config.Distance && currentPage < total - 1) Next()
if (endY - startY > childH / config.Distance) Prev()
parentEle.style.transform = `translate3d(0,${-1 * (childH * currentPage)}px,0)`
break
}
break
case 'horizontal':
default:
endX = pageX - left
switch (Effect) {
case 'fade':
if (startX - endX > childW / Distance && currentPage === total - 1) {
currentPage = 0
} else if (startX - endX > childW / Distance && currentPage < total - 1) {
Next()
} else if (endX - startX > childW / Distance) {
Prev()
}
for (let cur of childEle) {
cur.style.transition = `opacity ${Speed}s ${Curve}`
cur.style.opacity = 0
cur.style.zIndex = 1
}
childEle[currentPage].style.opacity = 1
childEle[currentPage].style.zIndex = 2
Swiper()
break
default:
if (startX - endX > childW / Distance && currentPage < total - 1) Next()
if (endX - startX > childW / Distance) Prev()
parentEle.style.transform = `translate3d(${-1 * (childW * currentPage)}px,0,0)`
break
}
break
}
}
//方法:滚动中
const scrollMove = event => {
event.preventDefault()
if (event.deltaY > 20 && !isScrolling) {
isScrolling = !0
Next()
}
if (event.deltaY < -20 && !isScrolling) {
isScrolling = !0
Prev()
}
}
//自动播放
let AutoPlayFrame
const AutoPlay = () => {
if (AutoSpeed) {
AutoTimeout = setTimeout(() => {
Next()
clearTimeout(AutoTimeout)
AutoPlayFrame = requestAnimationFrame(AutoPlay)
}, AutoSpeed * 1000)
}
}
//初始化
function Init() {
const { width, height } = parentEle.parentElement.getBoundingClientRect()
childW = width
childH = height
currentPage = InitPage
return new Promise(next => {
switch (Effect) {
case 'fade':
for (let cur of childEle) {
cur.style.transition = `opacity ${Speed}s ${Curve}`
cur.style.position = 'absolute'
}
parentEle.style.width = `${width}px`
parentEle.style.height = `${height}px`
break
default:
switch (Direction) {
case 'vertical':
parentEle.style.width = `${width}px`
parentEle.style.height = `${height * total}px`
parentEle.style.flexDirection = 'column'
parentEle.style.cssText += 'touch-action: pan-x'
break
case 'horizontal':
default:
parentEle.style.width = `${width * total}px`
parentEle.style.height = `${height}px`
parentEle.style.flexDirection = 'row'
parentEle.style.cssText += 'touch-action: pan-y'
break
}
parentEle.style.display = 'flex'
parentEle.style.transition = `transform ${Speed}s ${Curve}`
break
}
//移除事件
Swiper(InitPage)
AutoPlay()
Inertia && parentEle.removeEventListener('touchmove', touchMove)
Scroll && parentEle.removeEventListener('mousewheel', scrollMove)
parentEle.removeEventListener('touchstart', touchStart)
parentEle.removeEventListener('touchend', touchEnd)
//添加事件
Inertia && parentEle.addEventListener('touchmove', touchMove)
Scroll && parentEle.addEventListener('mousewheel', scrollMove)
parentEle.addEventListener('touchstart', touchStart)
parentEle.addEventListener('touchend', touchEnd)
if (Hover) {
parentEle.addEventListener('mouseover', () => {
clearTimeout(AutoTimeout)
cancelAnimationFrame(AutoPlayFrame)
})
parentEle.addEventListener('mouseout', AutoPlay)
}
next()
})
}
this.prev = Prev
this.next = Next
this.direct = index => {
isFristTime = !1
Swiper(index)
}
this.disable = () => {
Inertia && parentEle.removeEventListener('touchmove', touchMove)
Scroll && parentEle.removeEventListener('mousewheel', scrollMove)
parentEle.removeEventListener('touchstart', touchStart)
parentEle.removeEventListener('touchend', touchEnd)
clearTimeout(AutoTimeout)
cancelAnimationFrame(AutoPlayFrame)
}
this.enable = () => {
//添加事件
Inertia && parentEle.addEventListener('touchmove', touchMove)
Scroll && parentEle.addEventListener('mousewheel', scrollMove)
parentEle.addEventListener('touchstart', touchStart)
parentEle.addEventListener('touchend', touchEnd)
if (Hover) {
parentEle.addEventListener('mouseover', () => {
clearTimeout(AutoTimeout)
cancelAnimationFrame(AutoPlayFrame)
})
parentEle.addEventListener('mouseout', AutoPlay)
}
if (AutoSpeed) {
AutoTimeout = setTimeout(() => {
Next()
clearTimeout(AutoTimeout)
AutoPlayFrame = requestAnimationFrame(AutoPlay)
}, AutoSpeed * 1000)
}
}
Init()
if (AutoResize) {
let isResizing = !1
let resizeTimer
w.onresize = () => {
clearTimeout(resizeTimer)
resizeTimer = null
if (!isResizing) {
isResizing = !0
resizeTimer = setTimeout(() => {
Init()
isResizing = !1
}, 300)
}
}
}
return this
}
//页面&字体自适应
AutoSize(options) {
let config = {
//固定尺寸(类型:字符串)
PageSize: 'device-width',
//初始缩放(类型:数字)
InitScale: 1,
//最小缩放(类型:数字)
MinScale: 1,
//最大缩放(类型:数字)
MaxScale: 1,
//DPI缩放(类型:数字;默认window.devicePixelRatio)
Ratio: null,
//窗口变化重新计算(类型:布尔)
Resize: !0,
//是否缩放字体大小(类型:布尔)
ScaleFont: !0,
}
config = this.extend(config, options)
const meta = document.createElement(`meta`),
{ PageSize, InitScale, MinScale, MaxScale, Resize, ScaleFont } = config
let Ratio = config.Ratio || w.devicePixelRatio
meta.setAttribute('name', 'viewport')
if (typeof PageSize !== 'number') {
meta.setAttribute('content', `width=${PageSize},initial-scale=${InitScale},minimum-scale=${MinScale},maximum-scale=${MaxScale},user-scalable=no,viewport-fit=cover`)
} else {
meta.setAttribute('content', `width=${PageSize},user-scalable=no,viewport-fit=cover`)
}
if (document.querySelector("meta[name='viewport']")) {
document.querySelector("meta[name='viewport']").remove()
}
document.head.appendChild(meta)
if (ScaleFont) {
//根据屏幕尺寸设置根元素字体大小
const SetSize = () => {
const { clientWidth: width, clientHeight: height } = document.documentElement
document.documentElement.style.fontSize = `${(Math.min(width, height) / 10) * Ratio}px`
}
SetSize()
if (Resize) w.onresize = SetSize
}
return this
}
//自定义弹框
Dialog(options) {
let config = {
//是否显示遮罩
mask: !0,
//遮罩颜色(类型:字符串)
maskColor: 'rgba(0,0,0,.85)',
//点击遮罩退出(类型:布尔)
maskOut: !0,
//过渡速度/毫秒(类型:数字)
Speed: 180,
//过渡曲线(类型字符串参考CSS3可用曲线)
Curve: 'ease-out',
//进出方式(类型字符串none:无、zoom:缩放、top:从屏幕上方出现、bottom:从屏幕下方出现)
Direction: 'zoom',
//进入事件(类型:方法)
In: null,
//退出事件(类型:方法)
Out: null,
//确认事件
Confirm: {
//确定按钮(类型:字符串)
btn: null,
//回调(类型:方法;返回类型:对象)
callback: null,
},
//取消事件
Cancel: {
//取消按钮(类型:字符串)
btn: null,
//回调(类型:方法;返回类型:对象)
callback: null,
},
}
config = this.extend(config, options)
const masker = document.createElement(`div`),
parent = this.get.parentElement,
{ mask, maskColor, maskOut, Speed, Curve, Direction, In, Out, Confirm, Cancel } = config
masker.className = 'Pd-Mask'
const confirmBtn = Confirm.btn ? new PandoraEX(Confirm.btn) : null,
cancelBtn = Cancel.btn ? new PandoraEX(Cancel.btn) : null
if (Direction !== 'none') this.css({ transition: `all ${Speed}ms ${Curve}` })
//关闭弹框
const closeDialog = () => {
return new Promise(next => {
if (this.css('display') == 'block' || this.css('display') == 'flex') {
Effect(`out`)
if (Direction === 'none') {
try {
mask && parent.removeChild(masker)
} catch (err) {
console.error(`[${Alphabet[8]} - Dialog]`, err)
}
this.css({ display: 'none' })
next()
} else {
this.bind('transitionend', () => {
try {
mask && parent.removeChild(masker)
} catch (err) {
console.error(`[${Alphabet[8]} - Dialog]`, err)
}
this.css({ display: 'none' })
this.unbind('transitionend')
next()
})
}
} else {
next()
}
Confirm.btn && confirmBtn.unbind(`click`)
Cancel.btn && cancelBtn.unbind(`click`)
w.onresize = null
})
}
//进入和退出效果
const Effect = where => {
if (mask && where === 'in') {
parent.insertBefore(masker, this.get.nextElementSibling)
document.querySelector(`.Pd-Mask`).style.cssText = `width:100vw;height:100vh;background:${maskColor};position:fixed;inset:0;top:0;left:0;right:0;bottom:0;z-index:998;`
}
switch (where) {
case 'in':
this.css({ display: 'block' })
switch (Direction) {
case 'none':
this.css({ display: 'block' })
break
case 'top':
this.css({ transform: 'translate3d(0,-100%,0)' })
break
case 'bottom':
this.css({ transform: 'translate3d(0,100%,0)' })
break
case 'zoom':
this.css({ transform: 'translate3d(0,0,0) scale(0)' })
break
}
In && In()
break
case 'out':
switch (Direction) {
case 'none':
this.css({ display: 'none' })
break
case 'top':
this.css({ transform: 'translate3d(0,-100%,0)' })
break
case 'bottom':
this.css({ transform: 'translate3d(0,100%,0)' })
break
case 'zoom':
this.css({ transform: 'translate3d(0,0,0) scale(0)' })
break
}
Out && Out()
break
}
}
//打开弹框
const openDialog = param => {
this.unbind('transitionend')
for (let a of w.pdDialogs) {
if (a != this) a.close()
}
Effect(`in`)
return new Promise(next => {
const calcDialog = () => {
const top = parseInt(this.css(`height`)) / 2,
left = parseInt(this.css(`width`)) / 2
switch (Direction) {
case 'none':
this.css({ position: 'fixed', top: `calc(50% - ${top}px)`, left: `calc(50% - ${left}px)`, 'z-index': 999 })
break
case 'top':
this.css({ position: 'fixed', top: 0, left: `calc(50% - ${left}px)`, 'z-index': 999, transform: 'translate3d(0,0,0) scale(1)' })
break
case 'bottom':
this.css({ position: 'fixed', bottom: 0, left: `calc(50% - ${left}px)`, 'z-index': 999, transform: 'translate3d(0,0,0) scale(1)' })
break
case 'zoom':
default:
this.css({ position: 'fixed', top: `calc(50% - ${top}px)`, left: `calc(50% - ${left}px)`, 'z-index': 999, transform: 'translate3d(0,0,0) scale(1)' })
break
}
}
calcDialog()
w.onresize = () => {
this.bind('transitionend', calcDialog)
}
//遮罩被点击
if (mask && maskOut) masker.onclick = closeDialog
const { close } = this
//确认按钮被点击
Confirm.btn &&
confirmBtn.bind('click', () => {
Confirm.callback({ param: param || null, close })
})
//取消按钮被点击
Cancel.btn &&
cancelBtn.bind('click', () => {
Cancel.callback({ param: param || null, close })
})
next()
})
}
this.close = closeDialog
this.open = openDialog
w.pdDialogs.push(this)
return this
}
//图片预加载
ImgLoader(options) {
let config = {
//渐进式(类型:布尔)
lazy: !0,
//加载中(类型:方法;返回类型:数字)
loading: null,
//加载完成(类型:方法)
callback: null,
//加载错误(类型:方法)
error(err) {
console.error(`[${Alphabet[8]} - ImgLoader] 资源加载错误!\n${err}`)
alert('资源加载错误!')
},
}
config = this.extend(config, options)
const { lazy, loading, callback, error } = config
let ImgArr = [],
total = 0,
cur = 0,
step = 0,
floatNum = 0
// 类型检查
const typeCheck = str => {
if (str.indexOf(`url`) > -1 && str != 'none' && str.indexOf(`data:`) < 0 && str.indexOf(`blob:`) < 0) {
return !0
} else {
return !1
}
}
// 仅容器内获取
const onlyContainer = (ele, callout) => {
const pattern = new RegExp('".*?"', 'g'),
pattern2 = new RegExp(/'.*?'/, 'g'),
pattern3 = new RegExp(/\(.*?\)/, 'g')
for (let e of ele.querySelectorAll('*')) {
if (e.nodeName.toLowerCase() == 'img') {
if (e.src) {
ImgArr.push(e.src)
}
}
const getBg = w.getComputedStyle(e).getPropertyValue(`background-image`)
if (typeCheck(getBg)) {
const url1 = getBg.match(pattern),
url2 = getBg.match(pattern2),
url3 = getBg.match(pattern3)
url1 && ImgArr.push(url1[0].toString().replace(/"/g, ''))
url2 && ImgArr.push(url2[0].toString().replace(/'/g, ''))
if (url3) {
let src = url3[0].toString().replace(/\(/, '').replace(/\)/, '')
if (src.match(pattern)) src = src.match(pattern)[0].toString().replace(/"/g, '')
if (src.match(pattern2)) src = src.match(pattern2)[0].toString().replace(/'/g, '')
ImgArr.push(src)
}
}
}
callout()
}
// 全局获取
const allResources = callout => {
const pattern1 = new RegExp(`background-image: url(.*?)*`, 'g'),
pattern2 = new RegExp(`background: url(.*?)*`, 'g')
// 截取背景图
const getThat = (type, str) => {
let src
switch (type) {
case 1:
src = str.split(`background-image: url(`)[1].split(`)`)[0]
break
case 2:
src = str.split(`background: url(`)[1].split(`)`)[0]
break
}
if (src.indexOf(`"`) > -1) return src.replace(/"/g, ``)
if (src.indexOf(`'`) > -1) return src.replace(/'/g, ``)
return src
}
document.querySelectorAll('link').forEach(css => {
if (css.getAttribute('rel') == 'stylesheet') {
this.ajax({
url: css.getAttribute('href'),
responseType: 'text',
}).then(str => {
if (str.match(pattern1)) {
for (let a of str.match(pattern1)) {
typeCheck(a) && ImgArr.push(getThat(1, a))
}
}
if (str.match(pattern2)) {
for (let a of str.match(pattern2)) {
typeCheck(a) && ImgArr.push(getThat(2, a))
}
}
})
}
})
document.querySelectorAll('style').forEach(css => {
const str = css.innerText
if (str.match(pattern1)) {
for (let a of str.match(pattern1)) {
typeCheck(a) && ImgArr.push(getThat(1, a))
}
}
if (str.match(pattern2)) {
for (let a of str.match(pattern2)) {
typeCheck(a) && ImgArr.push(getThat(2, a))
}
}
})
const bodyStr = document.body.innerHTML
if (bodyStr.match(pattern1)) {
for (let a of bodyStr.match(pattern1)) {
typeCheck(a) && ImgArr.push(getThat(1, a))
}
}
if (bodyStr.match(pattern2)) {
for (let a of bodyStr.match(pattern2)) {
typeCheck(a) && ImgArr.push(getThat(2, a))
}
}
for (let e of document.querySelectorAll('img')) {
e.src && ImgArr.push(e.src)
}
callout()
}
// 加载全部并回调
const finalStep = () => {
total = ImgArr.length
const loader = src => {
return new Promise((success, fail) => {
const img = new Image()
img.src = src
img.onerror = fail
if (img.complete) {
cur++
success()
} else {
img.onload = () => {
cur++
success()
}
}
})
}
//加载中
let loadStepFrame
Promise.all(ImgArr.map(e => loader(e))).catch(err => {
cancelAnimationFrame(loadStepFrame)
error(err)
console.error(`[${Alphabet[8]} - ImgLoader]`, err)
})
const loadStep = () => {
step = Math.floor((cur / total) * 100)
if (floatNum < 100) {
if (floatNum < step) lazy ? floatNum++ : (floatNum = step)
loading && loading(floatNum)
if (floatNum === 100) {
cancelAnimationFrame(loadStepFrame)
if (lazy) {
callback && callback()
} else {
setTimeout(callback)
}
} else {
loadStepFrame = requestAnimationFrame(loadStep)
}
}
}
loadStep()
}
this.get ? onlyContainer(this.get, finalStep) : allResources(finalStep)
return this
}
//图片上传
ImgUpload(options) {
let config = {
//接口地址(类型:字符串)
apiUrl: null,
//格式限制(类型:字符串)
Format: '*',
//选择类型(可选参数default、camera)
type: 'default',
//限制数量(类型:数字)
Max: 1,
//压缩比例(类型:数字)
Quality: 100,
//尺寸裁切
Clip: {
//宽度(类型:数字)
width: null,
//高度(类型:数字)
height: null,
},
//是否总是覆盖
alwaysCover: !1,
//上传事件
Events: {
//超过限制(类型:方法)
overMax: null,
//开始上传(类型:方法)
ready: null,
//上传中(类型:方法;返回类型:数字)
progress: null,
//上传成功(类型:方法;返回类型:对象)
success: null,
//失败(类型:方法)
fail() {
console.error(`[${Alphabet[8]} - ImgUpload] 上传失败!`)
},
},
//唯一id(类型字符串如果为null则启用临时上传请谨慎使用)
Uid: null,
}
config = this.extend(config, options)
const innerHtml = this.html(),
{ apiUrl, Format, type, Max, Quality, Clip, alwaysCover, Events, Uid } = config
this.empty()
this.get.insertAdjacentHTML('afterbegin', `<label for="Pd_imgUpload_${this.pid}" style="width:100%;height:100%;display:block"></label>`)
const uploadBtn = document.createElement(`input`)
let userId,
total = 0,
currentIndex = 0,
stepsTotal = 0,
stepsOnly = 0,
finalTotal = 0
if (Uid) {
userId = Uid
} else {
userId = `${document.domain}_${this.pid}`
}
uploadBtn.type = 'file'
uploadBtn.accept = `image/${Format}`
uploadBtn.id = `Pd_imgUpload_${this.pid}`
uploadBtn.setAttribute('capture', type)
uploadBtn.hidden = !0
if (Max > 1) uploadBtn.multiple = !0
const label = this.get.querySelector(`label`)
label.innerHTML = innerHtml
label.append(uploadBtn)
//上传图片
const uploadPreview = obj => {
const formData = new FormData()
let waitUploadFile = obj
if (alwaysCover) waitUploadFile = new File([obj], `cover.${obj.name.split('.')[1]}`, { type: obj.type })
formData.append('images', waitUploadFile)
formData.append('uid', userId)
formData.append('width', Clip.width)
formData.append('height', Clip.height)
formData.append('quality', Quality)
Events.ready && Events.ready()
this.ajax({
url: `${apiUrl}`,
type: 'post',
dataType: 'form',
async: !0,
headers: null,
data: formData,
progress(progress) {
if (total > 1) {
if (progress == 100) currentIndex++
stepsTotal = Math.floor((currentIndex / total) * 100)
Events.progress(stepsTotal)
} else {
stepsOnly = progress
Events.progress(stepsOnly)
}
},
})
.then(res => {
if (res) {
if (total > 1) {
finalTotal = stepsTotal
} else {
finalTotal = stepsOnly
}
uploadBtn.setAttribute('data-progress', finalTotal)
if (finalTotal === 100) {
const data = { src: res.images }
Events.success && Events.success(data)
}
} else {
alert('发生错误!')
console.error(`[${Alphabet[8]} - ImgUpload] 服务端错误!`)
}
})
.catch(Events.fail)
}
//获取选择文件
const selectedFile = Files => {
const files = Array.prototype.slice.call(Files)
;(total = 0), (currentIndex = 0), (stepsTotal = 0), (stepsOnly = 0), (finalTotal = 0)
if (Max === 0 || files.length <= Max) {
total = files.length
if (total > 0) {
files.forEach((file, idx) => {
uploadPreview(Files[idx])
})
}
} else {
Events.overMax && Events.overMax()
console.info(`[${Alphabet[7]} - ImgUpload] 超过最大数量:${Max}`)
}
}
//选择文件按钮事件
uploadBtn.addEventListener('change', event => {
event.preventDefault()
selectedFile(event.target.files)
})
//拖动文件事件
this.bind('dragover', event => {
event.preventDefault()
})
this.bind('drop', event => {
event.preventDefault()
selectedFile(event.dataTransfer.files)
})
return this
}
//图片移动缩放
ImgTransit(options) {
let config = {
//显示控制图标(类型:布尔)
icon: !0,
//控制图标大小(类型:数字)
iconSize: 30,
//显示边框(类型:布尔)
border: !0,
//开启多点触控(类型:布尔)
Gesture: !1,
//内边距(类型:数字)
padding: 10,
//缩放
scale: {
//是否启用(类型:布尔)
enable: !0,
//最小(类型:数字)
min: 80,
//最大(类型:数字)
max: 150,
//速率(类型:数字)
rate: 1,
},
//旋转
rotate: {
//是否启用(类型:布尔)
enable: !0,
//速率(类型:数字)
rate: 1,
},
//是否启用删除(类型:布尔)
delete: !0,
//边界限制(类型:布尔)
bounds: !0,
//边界可超出范围(类型:数字)
outBounds: 0,
//操作回调方法(类型:方法;返回类型:对象)
callback: null,
}
config = this.extend(config, options)
const that = this.get,
btnAnimation = 'transition:opacity .2s ease-in',
imgs = that.querySelectorAll('img')
this.hide()
// 创建容器
if (!document.querySelector('.PD-TransitBox')) {
that.insertAdjacentHTML('afterend', "<div class='PD-TransitBox'></div>")
document.querySelector('.PD-TransitBox').style.cssText = `position:relative;touch-action:none;`
}
const TransitBox = document.querySelector('.PD-TransitBox')
for (let the of imgs) {
TransitBox.appendChild(the)
the.parentNode.removeChild(the)
}
//图标配置
const iconStyle = option => {
let positionConfig = { top: null, left: null, right: null, bottom: null, name: null }
positionConfig = this.extend(positionConfig, option)
return `<a class="Pd-ImgTransit-btn Pd-${positionConfig.name}" style="width:${config.iconSize}px;height:${config.iconSize}px;background:#fff url('${
icoConfig[positionConfig.name]
}');background-position:center;background-repeat:no-repeat;background-size:65%;position:absolute;border-radius:50%;top:${positionConfig.top}px;left:${positionConfig.left}px;right:${positionConfig.right}px;bottom:${
positionConfig.bottom
}px;z-index:2;${btnAnimation}" href="javascript:void 0"></a>`
}
const icon = {
resize: iconStyle({ left: `-${config.iconSize / 2}`, bottom: `-${config.iconSize / 2}`, name: 'resize' }),
rotate: iconStyle({ right: `-${config.iconSize / 2}`, top: `-${config.iconSize / 2}`, name: 'rotate' }),
delete: iconStyle({ left: `-${config.iconSize / 2}`, top: `-${config.iconSize / 2}`, name: 'delete' }),
}
//设置参数
const setConfig = (ele, eleConfig) => {
for (let a of ele.querySelectorAll(`.Pd-ImgTransit-btn`)) a.style.transform = `scale(${1 / (eleConfig.scale / 100)}) rotate(${-1 * eleConfig.rotate}deg)`
return (ele.style.transform = `translate3d(${eleConfig.translate}) scale(${eleConfig.scale / 100}) rotate(${eleConfig.rotate}deg)`)
}
//获取中心
const getCenterPoint = ele => {
return {
x: ele.getBoundingClientRect().left + ele.offsetWidth / 2,
y: ele.getBoundingClientRect().top + ele.offsetHeight / 2,
}
}
// 获取两点距离
const getDistance = (p1, p2) => {
const x = p2.pageX - p1.pageX,
y = p2.pageY - p1.pageY
return Math.sqrt(x * x + y * y)
}
// 获取两点角度
const getAngle = (p1, p2) => {
const x = p1.pageX - p2.pageX,
y = p1.pageY - p2.pageY
return (Math.atan2(y, x) * 180) / Math.PI
}
// 多点触控
const setGesture = el => {
let obj = {} //定义一个对象
let isTouch = !1
let start = []
el.addEventListener(
'touchstart',
e => {
if (e.touches.length >= 2) {
//判断是否有两个点在屏幕上
isTouch = !0
start = e.touches //得到第一组两个点
obj.gesturestart && obj.gesturestart.call(el) //执行gesturestart方法
}
},
!1
)
document.addEventListener(
'touchmove',
e => {
e.preventDefault()
if (e.touches.length >= 2 && isTouch) {
const now = e.touches, //得到第二组两个点
scale = getDistance(now[0], now[1]) / getDistance(start[0], start[1]), //得到缩放比例getDistance是勾股定理的一个方法
rotation = getAngle(now[0], now[1]) - getAngle(start[0], start[1]) //得到旋转角度getAngle是得到夹角的一个方法
e.scale = scale.toFixed(2)
e.rotation = rotation.toFixed(2)
obj.gesturemove && obj.gesturemove.call(el, e) //执行gesturemove方法
}
},
!1
)
document.addEventListener(
'touchend',
() => {
if (isTouch) {
isTouch = !1
obj.gestureend && obj.gestureend.call(el) //执行gestureend方法
}
},
!1
)
return obj
}
// 添加事件
const addEvent = ele => {
//添加容器事件
let touchStart,
touchEnd,
touchMove,
touchResize,
touchRotate,
touchDelete,
centerPoint,
prevAngle,
touchX = 0,
touchY = 0,
startX = 0,
startY = 0,
prevScale = 100
const eleReal = ele,
eleConfig = { translate: `0,0,0`, scale: 100, rotate: 0 },
w = eleReal.offsetWidth,
h = eleReal.offsetHeight
eleReal.style.width = `${w}px`
eleReal.style.height = `${h}px`
setConfig(eleReal, eleConfig)
eleReal.style.position = 'absolute'
eleReal.style.top = eleReal.style.left = '50%'
eleReal.style.margin = `-${h / 2 + config.padding}px 0 0 -${w / 2 + config.padding}px`
eleReal.style.padding = `${config.padding}px`
//选中元素
touchStart = event => {
event.preventDefault()
if (event.target.className.indexOf('pd_child') < 0) {
startX = event.changedTouches[0].pageX - touchX
startY = event.changedTouches[0].pageY - touchY
event.target.style.transform = 'scale(1.04)'
config.callback && config.callback({ type: 'choose', obj: event.target.parentElement })
}
}
touchEnd = event => {
if (event.target.className.indexOf('pd_child') < 0) event.target.style.transform = 'scale(1)'
}
//移动事件
touchMove = event => {
if (event.touches.length < 2 && event.target.className.indexOf('pd_child') < 0) {
const nowX = event.changedTouches[0].pageX,
nowY = event.changedTouches[0].pageY,
w = event.target.getBoundingClientRect().width,
h = event.target.getBoundingClientRect().height,
icon = event.target.parentElement.querySelectorAll(`.Pd-ImgTransit-btn`)[0].getBoundingClientRect(),
iconW = icon.width / 2,
getBounding = event.target.parentElement.parentElement.getBoundingClientRect(),
parentBox = {
width: config.bounds ? getBounding.width + config.outBounds : getBounding.width,
height: config.bounds ? getBounding.height + config.outBounds : getBounding.height,
}
touchX = nowX - startX
touchY = nowY - startY
if (config.bounds) {
if (Math.abs(touchX) >= parentBox.width / 2 - w / 2 - iconW) {
if (touchX < 0) {
touchX = -1 * (parentBox.width / 2 - w / 2 - iconW)
} else {
touchX = parentBox.width / 2 - w / 2 - iconW
}
}
if (Math.abs(touchY) >= parentBox.height / 2 - h / 2 - iconW) {
if (touchY < 0) {
touchY = -1 * (parentBox.height / 2 - h / 2 - iconW)
} else {
touchY = parentBox.height / 2 - h / 2 - iconW
}
}
}
eleConfig.translate = `${touchX}px,${touchY}px,0`
setConfig(eleReal, eleConfig)
config.callback && config.callback({ type: 'move', obj: eleReal })
}
}
//缩放事件
touchResize = event => {
event.stopImmediatePropagation()
event.preventDefault()
const x = event.changedTouches[0].pageX - eleReal.getBoundingClientRect().left
if (x > 0 && eleConfig.scale > config.scale.min) eleConfig.scale -= config.scale.rate
if (x < 0 && eleConfig.scale < config.scale.max) eleConfig.scale += config.scale.rate
if (event.touches.length >= 2) {
if (config.scale.enable) {
prevScale = event.scale * 100
eleConfig.scale = prevScale
}
if (config.rotate.enable) eleConfig.rotate = event.rotation
}
setConfig(eleReal, eleConfig)
config.callback && config.callback({ type: 'resize', obj: eleReal })
}
//旋转事件
touchRotate = event => {
event.stopImmediatePropagation()
event.preventDefault()
const angle = Math.atan2(event.changedTouches[0].pageY - centerPoint.y, event.changedTouches[0].pageX - centerPoint.x)
eleConfig.rotate = Math.floor(((angle - prevAngle) * 180) / Math.PI) * config.rotate.rate
setConfig(eleReal, eleConfig)
config.callback && config.callback({ type: 'rotate', obj: eleReal })
}
//删除事件
touchDelete = event => {
event.stopImmediatePropagation()
event.preventDefault()
eleConfig.translate = '0,0,0'
eleConfig.rotate = 0
eleConfig.scale = 100
setConfig(eleReal, eleConfig)
eleReal.style.display = 'none'
config.callback && config.callback({ type: 'delete', obj: eleReal })
}
//绑定所有操作
eleReal.addEventListener('touchstart', touchStart)
eleReal.addEventListener('touchend', touchEnd)
eleReal.addEventListener('touchmove', touchMove)
if (config.scale.enable && config.rotate.enable && config.Gesture) {
setGesture(eleReal).gesturemove = e => {
touchResize(e)
touchRotate(e)
}
}
if (config.icon && config.scale.enable) eleReal.querySelectorAll(`.Pd-resize`)[0].addEventListener('touchmove', touchResize)
if (config.icon && config.rotate.enable) {
eleReal.querySelectorAll(`.Pd-rotate`)[0].addEventListener('touchstart', event => {
centerPoint = getCenterPoint(eleReal)
prevAngle = Math.atan2(event.changedTouches[0].pageY - centerPoint.y, event.changedTouches[0].pageX - centerPoint.x) - (eleConfig.rotate * Math.PI) / 180
})
eleReal.querySelectorAll(`.Pd-rotate`)[0].addEventListener('touchmove', touchRotate)
}
if (config.icon && config.delete) eleReal.querySelectorAll(`.Pd-delete`)[0].addEventListener('touchstart', touchDelete)
}
//隐藏操作按钮
const hideBtn = () => {
const allCon = TransitBox.querySelectorAll(`.Pd-ImgTransit`),
allBtn = TransitBox.querySelectorAll(`.Pd-ImgTransit-btn`)
for (let a of allCon) {
a.style.border = 'none'
a.style.zIndex = 1
}
for (let a of allBtn) a.style.opacity = 0
}
//显示操作按钮
const showBtn = tag => {
const curBtn = tag.querySelectorAll(`.Pd-ImgTransit-btn`)
for (let a of curBtn) {
a.style.opacity = 1
if (config.border) tag.style.border = '2px dashed white'
tag.style.zIndex = 2
}
}
// 初始化
for (let the of imgs) {
let btn = ''
if (config.icon) {
config.scale.enable && (btn += icon.resize)
config.rotate.enable && (btn += icon.rotate)
config.delete && (btn += icon.delete)
}
const TransitElement = document.createElement('div')
TransitElement.className = `Pd-ImgTransit pd_child_${the.alt}`
TransitElement.style.position = 'absolute'
the.style.transition = 'transform .4s ease-in'
TransitElement.innerHTML = btn
TransitElement.appendChild(the)
TransitBox.appendChild(TransitElement)
addEvent(TransitElement)
}
hideBtn()
//显示当前按钮
TransitBox.addEventListener('touchstart', event => {
hideBtn()
if (event.target !== TransitBox && event.target.className.indexOf('pd_child') < 0) {
config.icon ? showBtn(event.target.parentElement) : hideBtn()
event.target.zIndex = 1
}
})
return this
}
//微信SDK
wxSDK(options) {
let config = {
//相关接口地址(类型:字符串)
apiUrl: null,
//分享sdk版本
sdk: 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js',
//分享标题(类型:字符串或数组)
title: ['分享至朋友圈', '分享至好友'],
//分享描述(类型:字符串)
desc: '万事皆虚,万物皆允',
//分享图(类型:字符串或数组)
shareIcon: `https://src.pandorastudio.cn/favicon.jpg`,
//分享链接(类型:字符串或数组)
shareLinks: w.location.href,
//调试(类型:布尔)
debug: !1,
//微信jsApiList(类型:数组)
jsApiList: null,
//开放标签列表(类型:数组)
openTagList: null,
//回调方法
callback: {
//分享就绪(类型:方法)
ready: null,
//分享成功(类型:方法)
success: null,
//分享失败或取消(类型:方法)
error: null,
},
}
config = this.extend(config, options)
const scriptTag = document.createElement('script')
let { apiUrl, sdk, title, desc, shareLinks, debug, jsApiList, openTagList, callback, shareIcon } = config
scriptTag.id = 'Pd_share'
scriptTag.src = `${sdk}?${new Date().getTime()}`
document.querySelector(`#Pd_share`) && document.querySelector(`#Pd_share`).remove()
document.body.appendChild(scriptTag)
let hasIcon = !1
const isObj = con => {
if (typeof con === 'object') {
return !0
} else {
return !1
}
}
document.querySelectorAll(`link`).forEach(tag => {
if (tag.getAttribute(`rel`) == 'shortcut icon') {
hasIcon = !0
shareIcon = tag.href
}
})
if (hasIcon) {
const link = document.createElement(`link`)
link.rel = 'shortcut icon'
link.href = isObj(shareIcon) ? shareIcon[0] : shareIcon
link.type = 'image/x-icon'
document.querySelector(`head`).appendChild(link)
}
let jsApiLists = ['onMenuShareTimeline', 'onMenuShareAppMessage', 'updateTimelineShareData', 'updateAppMessageShareData']
let openTagLists = ['wx-open-launch-app']
if (jsApiList) {
jsApiList.map(e => {
jsApiLists.push(e)
})
}
if (openTagList) {
openTagList.map(e => {
openTagLists.push(e)
})
}
const timeLine = {
title: isObj(title) ? title[0] : title,
link: isObj(shareLinks) ? shareLinks[0] : w.location.href.split(`#`)[0],
imgUrl: isObj(shareIcon) ? shareIcon[0] : shareIcon,
},
friend = {
title: isObj(title) ? title[1] : title,
link: isObj(shareLinks) ? shareLinks[1] : w.location.href.split(`#`)[0],
imgUrl: isObj(shareIcon) ? shareIcon[1] : shareIcon,
desc,
}
const success = res => {
const { appId, timestamp, nonceStr, signature } = res
wx.config({ debug, appId, timestamp, nonceStr, signature, jsApiList: jsApiLists, openTagList: openTagLists })
wx.ready(() => {
new Promise(next => {
if (wx.updateTimelineShareData) {
const { title, link, imgUrl } = timeLine
const { success, error } = callback
wx.updateTimelineShareData({ title, link, imgUrl, success, error })
}
if (wx.updateAppMessageShareData) {
const { title, link, imgUrl, desc } = friend
const { success, error } = callback
wx.updateAppMessageShareData({ title, desc, link, imgUrl, success, error })
}
next()
})
.then(callback.ready)
.catch(err => {
console.error(`[${Alphabet[8]} - wxSDK]`, err)
})
})
}
scriptTag.onload = () => {
this.ajax({ url: `${apiUrl}${w.location.href.split(`#`)[0]}` }).then(success)
}
return this
}
//懒加载
LazyLoad(options) {
let config = {
//缺省尺寸
width: 100,
height: 100,
//缺省图标(类型:字符串)
icon: icoConfig.load,
}
config = this.extend(config, options)
const imgArr = this.child(`img`).get,
{ width, height, icon } = config
let cur = 0,
lazyArr = []
//遍历所有图片
for (let img of imgArr) {
if (img.dataset.src) {
img.width = width
img.height = height
img.style.background = `url("${icon}") no-repeat center,black`
img.style.backgroundSize = `20%`
lazyArr.push(img)
}
}
//进入视图
const inView = obj => {
if (obj.getBoundingClientRect().y - w.innerHeight < 0) return obj
return !1
}
//检测图片状态
const checker = () => {
lazyArr.forEach(img => {
if (inView(img) && !img.src && img.complete) {
img.src = inView(img).dataset.src
img.style.transition = 'all .8s ease'
img.onload = () => {
let newWidth = Number(img.dataset.width) || img.naturalWidth,
newHeight = Number(img.dataset.height) || img.naturalHeight
if (img.dataset.width) newHeight = (newWidth / img.naturalWidth) * img.naturalHeight
if (img.dataset.height) newWidth = (newHeight / img.naturalHeight) * img.naturalWidth
img.width = newWidth
img.height = newHeight
img.removeAttribute('data-src')
img.style.background = null
img.dataset.width && img.removeAttribute('data-width')
img.dataset.height && img.removeAttribute('data-height')
cur++
if (cur == lazyArr.length) w.removeEventListener('scroll', checker)
img.addEventListener('transitionend', () => {
img.style.transition = null
})
}
img.onerror = () => {
console.error(`[${Alphabet[8]} - LazyLoad] 发生错误:${img.src}`)
cur++
}
}
})
}
//页面滚动事件
w.addEventListener('scroll', checker)
checker()
return this
}
//上传OSS
ossUpload(options) {
let config = {
// 阿里云账号AccessId(类型:字符串)
AccessId: null,
// 阿里云账号AccessKey(类型:字符串)
AccessKey: null,
// OSS Bucket 外网域名(类型:字符串)
Endpoint: null,
// 文件大小限制(类型整数单位MB)
maxSize: 2,
}
config = this.extend(config, options)
const that = this
const { AccessId, AccessKey, Endpoint, maxSize } = config
// 引用检测
try {
Crypto
} catch (err) {
return console.error(`[${Alphabet[8]}] 缺少Crypto工具方法`, err)
}
if (!Crypto.HMAC) return console.error(`[${Alphabet[8]}] 缺少Crypto工具的HMAC方法`)
if (!Crypto.SHA1) return console.error(`[${Alphabet[8]}] 缺少Crypto工具的SHA1方法`)
// 配置检测
if (!AccessId) return console.error(`[${Alphabet[8]}] 缺失阿里云账号AccessId`)
if (!AccessKey) return console.error(`[${Alphabet[8]}] 缺失阿里云账号AccessKey`)
if (!Endpoint) return console.error(`[${Alphabet[8]}] 缺失OSS Bucket 外网域名`)
// 自动补零
const padThat = str => {
return str.toString().padStart(2, 0)
}
// 客户端签名
const policyText = {
// 获取失效时间
expiration: (function () {
let date
const Year = new Date().getFullYear(),
Month = new Date().getMonth() + 1,
Day = new Date().getDate(),
Hours = new Date().getHours()
date = `${Year}-${padThat(Month)}-${padThat(Day)}T${padThat(Hours)}:00:00.000Z`
return date
})(),
conditions: [
['content-length-range', 0, maxSize * 1024 * 1024], // 设置上传文件的大小限制
],
}
// 获取拓展名
const getSuffix = filename => {
return filename.substring(filename.lastIndexOf('.'))
}
// 上传文件
const uploadFile = obj => {
let {
// 待上传文件(类型:二进制)
fileObj = null,
// 文件名(类型:字符串,默认生成随机文件名)
fileName = that.guid(),
// 上传目录名称(类型:字符串,默认根目录)
dirName = '',
// 上传请求头(类型:对象)
headers = null,
// 是否开启同步上传(类型:布尔值)
async = !0,
// 上传中回调(类型:方法;返回类型:数字)
progress = null,
} = obj
const policyBase64 = OSSBase64.encode(JSON.stringify(policyText)),
bytes = Crypto.HMAC(Crypto.SHA1, policyBase64, AccessKey, { asBytes: !0 }),
signature = Crypto.util.bytesToBase64(bytes)
return new Promise((resolve, reject) => {
if (fileObj) {
if (fileObj.size > maxSize * 1024 * 1024) {
reject('overSize')
return console.warn(`[${Alphabet[9]}] 文件大小超出最大限制`)
} else {
const formData = new FormData()
formData.append('name', `${fileName}${getSuffix(fileObj.name)}`)
const copyFile = new File([fileObj], `${fileName}${getSuffix(fileObj.name)}`, { type: fileObj.type })
fileObj = copyFile
formData.append('key', dirName ? `${dirName}/\${filename\}` : '${filename}')
formData.append('policy', policyBase64)
formData.append('OSSAccessKeyId', AccessId)
formData.append('success_action_status', '200')
formData.append('signature', signature)
formData.append('file', fileObj)
that
.ajax({
url: Endpoint,
type: 'post',
headers,
async,
dataType: 'form',
data: formData,
progress,
})
.then(() => {
resolve({
code: 200,
msg: '上传成功',
url: (function () {
let url
if (dirName != '') {
url = `${Endpoint}/${dirName}/${fileObj.name}`
} else {
url = `${Endpoint}/${fileObj.name}`
}
return url
})(),
})
})
.catch(reject)
}
} else {
reject('noFile')
return console.warn(`[${Alphabet[9]}] 请选择需要上传的文件`)
}
})
}
this.start = uploadFile
return this
}
}
}
const Pandora = class extends PandoraJs(PandoraEX) {
constructor(obj = null) {
super(obj)
}
}
w.Pandora = Pandora
// 判断是否引用了JQuery或者Zepto
if (!w.$) {
w.$ = obj => {
return new Pandora(obj)
}
}
})(window || global || self || this)