adList.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. /**
  2. * 创建广告
  3. * config 参数说明
  4. * @param {string[]} ad_id 广告id
  5. * @param {string} ad_type 广告类型 1:视频 2:图片
  6. * @param {string} ad_pos 广告位置
  7. * @param {string[]} ad_size 广告尺寸
  8. * @param {string} ad_text 广告文案
  9. * @param {string[]} ad_url 广告链接
  10. * @param {string[]} ad_cover 广告图片
  11. */
  12. document.addEventListener('DOMContentLoaded', function () {
  13. const agreement =
  14. location.origin === 'http://' ? 'http:' : location.origin === 'https://' ? 'https:' : 'http://'
  15. const base = agreement + 'ads-qidian.sxtvs.com/'
  16. /**
  17. * 广告初始化
  18. * @param config 广告配置
  19. * @returns
  20. */
  21. function ad_init(config = {}) {
  22. // 启动上报
  23. const slots = config.slots || []
  24. const D = new Date()
  25. const dateTime = D.getTime()
  26. for (let i = 0; i < slots.length; i++) {
  27. const v = slots[i]
  28. // 获取对应广告位置
  29. const ad_pos = document.querySelector('#sxtv-ad-' + v.slotId)
  30. if (!ad_pos) {
  31. console.error('广告位置不存在:#sxtv-ad-' + v.slotId)
  32. continue
  33. }
  34. ad_pos.style.width = (v.width || 0) + 'px'
  35. ad_pos.style.height = (v.height || 0) + 'px'
  36. ad_pos.style.overflow = 'hidden'
  37. ad_pos.style.position = 'relative'
  38. if (!v.creativesList || !v.creativesList.length) {
  39. if (v.baseMaterial) ad_pos.appendChild(generateDefaultAd(v))
  40. continue
  41. }
  42. const gg = document.createElement('div')
  43. gg.innerHTML =
  44. '<svg t="1734493782806" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9775" width="32" height="32"><path d="M560.88 658.8h69.68v15.44h-69.68z" p-id="9776" fill="#515151"></path><path d="M698.24 451.44V200h-46.56v251.44H372.32V200h-46.56v251.44H186V824h651.92V451.44zM505.12 600H396.16v90.72L352 700.08v-33.92l8-2.4V575.2h54.24l-1.84-8.8h45.84l1.68 8.8h44.88z m162.72 96.48H524.4v-60h143.44z m5.36-64.48H518.88v-23.36h61.12v-11.28h-22.16l-2.88 8.48H520V582.4h8.72l5.52-13.76H568l-2.32 6.64h14.32v-8h38.64v8h49.04v22h-49.04v11.36h54.56z" p-id="9777" fill="#515151"></path></svg>'
  45. gg.style.position = 'absolute'
  46. gg.style.top = 0
  47. gg.style.right = 3
  48. gg.style.zIndex = 999999
  49. ad_pos.appendChild(gg)
  50. // 判定当前时间是否在creativesList的时间内
  51. let show = false
  52. for (let o = 0; o < v.creativesList.length; o++) {
  53. const item = v.creativesList[o]
  54. const start = new Date(item.startDate + ' 00:00:00').getTime()
  55. const end = new Date(item.endDate + ' 23:59:59').getTime()
  56. // 判断变量是否数组
  57. if (dateTime < start || dateTime > end || !Array.isArray(item.stuffsList)) {
  58. if (v.baseMaterial) ad_pos.appendChild(generateDefaultAd(v))
  59. continue
  60. }
  61. // 获取广告类型 1 轮播 2 交替 3 单项
  62. const showType = item.showType
  63. let e =
  64. showType === 1
  65. ? generateCarouselAd(item, D, v.slotId, v.width || 0, v.height || 0)
  66. : generateAlternateAd(item, D, v.slotId, v.width || 0, v.height || 0)
  67. if (e == -1 && !v.baseMaterial) continue
  68. // 使用打底素材
  69. if (e == -1) e = generateDefaultAd(v)
  70. show = true
  71. ad_pos.appendChild(e)
  72. }
  73. ad_pos.style.display = show ? 'block' : 'none'
  74. }
  75. }
  76. // 投放打底素材
  77. function generateDefaultAd(v) {
  78. console.log('投放打底素材', v)
  79. const fileType = v.baseMaterial.split('.').pop()
  80. if (fileType === 'jpg' || fileType === 'png' || fileType === 'gif') {
  81. const son_ele = document.createElement('img')
  82. son_ele.src = v.baseMaterial
  83. son_ele.style.width = '100%'
  84. son_ele.style.height = '100%'
  85. return son_ele
  86. }
  87. const son_ele = document.createElement('video')
  88. son_ele.setAttribute('loop', 'loop')
  89. son_ele.setAttribute('autoplay', 'autoplay')
  90. son_ele.muted = true
  91. son_ele.src = v.baseMaterial
  92. son_ele.style.width = '100%'
  93. son_ele.style.height = '100%'
  94. son_ele.oncanplay = () => son_ele.play()
  95. // 对body添加一次性点击事件
  96. const play = () => {
  97. son_ele.play()
  98. // 移除点击
  99. document.body.removeEventListener('click', play)
  100. document.body.removeEventListener('mousemove', play)
  101. }
  102. // 鼠标移动时间
  103. document.body.addEventListener('mousemove', play)
  104. document.body.addEventListener('click', play)
  105. return son_ele
  106. }
  107. // 生成轮播广告
  108. const generateCarouselAd = function (generateCarousel = {}, D, slotId, width, height) {
  109. const week = D.getDay() === 0 ? 6 : D.getDay() - 1
  110. const timeInterval = (generateCarousel.timeInterval || '')
  111. .slice(week * 24, week * 24 + 24)
  112. .split('')
  113. const H = D.getHours()
  114. if (generateCarousel.intervalType === 2 && timeInterval[H] == 0) return -1
  115. if (!generateCarousel.stuffsList || !generateCarousel.stuffsList.length) return -1
  116. const T = (generateCarousel.showIntervalTime || 5) * 1000
  117. return createCarousel(generateCarousel.stuffsList, T, slotId, width, height)
  118. }
  119. // 生成交替广告
  120. const generateAlternateAd = function (generateAlternate = {}, D, slotId, width, height) {
  121. const week = D.getDay() === 0 ? 6 : D.getDay() - 1
  122. const timeInterval = (generateAlternate.timeInterval || '')
  123. .slice(week * 24, week * 24 + 24)
  124. .split('')
  125. const H = D.getHours()
  126. if (generateAlternate.intervalType === 2 && timeInterval[H] == 0) return -1
  127. const stuff =
  128. generateAlternate.stuffsList[
  129. Math.floor((H * 60 + D.getMinutes()) / (generateAlternate.showIntervalTime || 5)) %
  130. generateAlternate.stuffsList.length
  131. ]
  132. if (!stuff) return -1
  133. const fileType = stuff.addr.split('.').pop()
  134. // fileType是否是图片类型
  135. if (fileType === 'jpg' || fileType === 'png' || fileType === 'gif') {
  136. const son_ele = document.createElement('img')
  137. son_ele.src = stuff.addr
  138. son_ele.style.width = width + 'px'
  139. son_ele.style.height = height + 'px'
  140. son_ele.onload = () => {
  141. const uuid = localStorage.getItem('ad_id')
  142. fetch(`${base}ad/show?uuid=${uuid}&stuffId=${stuff.stuffId}&slotId=${slotId}`).then((res) =>
  143. res.text(),
  144. )
  145. }
  146. son_ele.addEventListener('click', () => {
  147. if (!stuff.landingPage) return
  148. const uuid = localStorage.getItem('ad_id')
  149. // 点击广告
  150. fetch(`${base}ad/click?uuid=${uuid}&stuffId=${stuff.stuffId}&slotId=${slotId}`)
  151. .then((res) => res.text())
  152. .then(() => {
  153. window.open(stuff.landingPage)
  154. })
  155. })
  156. return son_ele
  157. }
  158. const son_ele = document.createElement('video')
  159. son_ele.setAttribute('loop', 'loop')
  160. son_ele.setAttribute('autoplay', 'autoplay')
  161. son_ele.muted = true
  162. son_ele.src = stuff.addr
  163. son_ele.style.width = '100%'
  164. son_ele.style.height = '100%'
  165. // 视频加载允许播放的回调
  166. son_ele.oncanplay = () => {
  167. son_ele.play()
  168. const uuid = localStorage.getItem('ad_id')
  169. fetch(`${base}ad/show?uuid=${uuid}&stuffId=${stuff.stuffId}&slotId=${slotId}`)
  170. }
  171. son_ele.addEventListener('click', () => {
  172. if (!stuff.landingPage) return
  173. const uuid = localStorage.getItem('ad_id')
  174. window.open(stuff.landingPage)
  175. // 点击广告
  176. fetch(`${base}ad/click?uuid=${uuid}&stuffId=${stuff.stuffId}&slotId=${slotId}`)
  177. })
  178. // 对body添加一次性点击事件
  179. const play = () => {
  180. son_ele.play()
  181. // 移除点击
  182. document.body.removeEventListener('click', play)
  183. document.body.removeEventListener('mousemove', play)
  184. }
  185. // 鼠标移动时间
  186. document.body.addEventListener('mousemove', play)
  187. document.body.addEventListener('click', play)
  188. return son_ele
  189. }
  190. function generateBrowserFingerprint() {
  191. return new Promise((resolve, reject) => {
  192. // 在这里执行获取指纹的操作,例如使用Canvas、WebGL等方法
  193. // 然后将生成的指纹字符串作为参数传递给resolve函数
  194. // 获取浏览器信息
  195. const browserInfo = `${navigator.userAgent} ${window.screen.width}x${window.screen.height}`
  196. // 获取时区偏移量
  197. const timezoneOffset = Intl.DateTimeFormat().resolvedOptions().timeZone
  198. // 在这里处理获取到的指纹信息
  199. const canvasFingerprint = getCanvasFingerprint()
  200. const webglFingerprint = getWebglFingerprint()
  201. // 将所有信息组合成一个字符串
  202. const fingerprintStr = `${browserInfo}|${timezoneOffset}|${canvasFingerprint}|${webglFingerprint}`
  203. // 使用createHash生成哈希值
  204. createHash(fingerprintStr)
  205. .then((hash) => {
  206. resolve(hash)
  207. })
  208. .catch((error) => {
  209. reject(error)
  210. })
  211. })
  212. }
  213. // Canvas指纹获取函数
  214. function getCanvasFingerprint() {
  215. const canvas = document.createElement('canvas')
  216. const ctx = canvas.getContext('2d')
  217. if (!ctx) return ''
  218. // 绘制一些内容
  219. ctx.fillStyle = '#f60'
  220. ctx.fillRect(0, 0, 16, 16)
  221. ctx.fillStyle = '#069'
  222. ctx.font = '11pt Arial'
  223. ctx.fillText('Cwm fjord!!', 4, 10)
  224. ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'
  225. ctx.fillText('Cwm fjord!!', 5, 11)
  226. ctx.scale(2, 2)
  227. ctx.fillStyle = '#fff'
  228. ctx.font = '24pt Arial'
  229. ctx.fillText('Cwm fjord!!', 10, 25)
  230. ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'
  231. ctx.fillText('Cwm fjord!!', 10, 25)
  232. const data = canvas.toDataURL()
  233. return data.replace(/data:image\/png;base64,/, '')
  234. }
  235. // WebGL指纹获取函数
  236. function getWebglFingerprint() {
  237. const canvas = document.createElement('canvas')
  238. const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
  239. if (!gl) return ''
  240. const extensions = gl.getSupportedExtensions()
  241. if (!extensions) return ''
  242. const extStr = extensions.join(',')
  243. const rendererInfo = gl.getParameter(gl.RENDERER) + '/' + gl.getParameter(gl.VENDOR)
  244. return rendererInfo + extStr
  245. }
  246. function createHash(str) {
  247. // 使用 SubtleCrypto API 计算 SHA-256 摘要
  248. return new Promise((resolve, reject) => {
  249. var script = document.createElement('script')
  250. script.type = 'text/javascript'
  251. script.src = agreement + 'assets.qidian.sxtvs.com/ads/jsSDK/crypto-js.min.js'
  252. // 脚本加载完成后的回调函数
  253. script.onload = function () {
  254. resolve(CryptoJS.SHA256(str).toString())
  255. }
  256. // 脚本加载失败的处理
  257. script.onerror = function () {
  258. console.error('无法生成广告指纹')
  259. reject('无法生成广告指纹')
  260. }
  261. // 将脚本元素追加到文档的<head>部分或者<body>部分
  262. document.head.appendChild(script)
  263. })
  264. }
  265. // 轮播
  266. function createCarousel(images, interval, slotId, width, height) {
  267. // 创建轮播图容器
  268. const carouselContainer = document.createElement('div')
  269. carouselContainer.style = `
  270. position: relative;
  271. width: 100%;
  272. height: 100%;
  273. margin: auto;
  274. overflow: hidden;
  275. `
  276. // 创建图片容器
  277. const imagesContainer = document.createElement('div')
  278. imagesContainer.style = `
  279. width: ${images.length * width}px;
  280. height: ${height}px;
  281. display: flex;
  282. transition: transform 0.5s ease;
  283. `
  284. // 创建指示器容器
  285. const indicatorsContainer = document.createElement('div')
  286. indicatorsContainer.style = `
  287. position: absolute;
  288. bottom: 10px;
  289. left: 50%;
  290. margin-right: 5px;
  291. transform: translateX(-50%);
  292. `
  293. // 添加图片到容器
  294. images.forEach((src, index) => {
  295. const img = document.createElement('img')
  296. img.src = src.addr
  297. img.alt = `Image ${index + 1}`
  298. img.style = `
  299. flex: 1;
  300. width: ${width}px;
  301. height: 100%;
  302. `
  303. img.addEventListener('click', () => {
  304. if (!src.landingPage) return
  305. const uuid = localStorage.getItem('ad_id')
  306. // 点击广告
  307. fetch(`${base}ad/click?uuid=${uuid}&stuffId=${src.stuffId}&slotId=${slotId}`)
  308. .then((res) => res.text())
  309. .then(() => {
  310. window.open(src.landingPage)
  311. })
  312. })
  313. // 图片加载完成
  314. img.onload = () => {
  315. const uuid = localStorage.getItem('ad_id')
  316. fetch(`${base}ad/show?uuid=${uuid}&stuffId=${src.stuffId}&slotId=${slotId}`).then((res) =>
  317. res.text(),
  318. )
  319. }
  320. imagesContainer.appendChild(img)
  321. // 添加指示器
  322. const indicator = document.createElement('button')
  323. indicator.style = `
  324. border: none;
  325. background-color: #fff;
  326. cursor: pointer;
  327. padding: 5px;
  328. border-radius: 50%;
  329. width: 10px;
  330. height: 10px;
  331. margin-right: 5px;
  332. display: inline-block;
  333. `
  334. if (index !== 0) indicator.style.backgroundColor = '#ccc'
  335. indicator.dataset.target = index
  336. indicatorsContainer.appendChild(indicator)
  337. })
  338. // 将图片容器和指示器容器添加到轮播图容器
  339. carouselContainer.appendChild(imagesContainer)
  340. carouselContainer.appendChild(indicatorsContainer)
  341. // 设置轮播图的切换逻辑
  342. let currentImageIndex = 0
  343. const maxImages = images.length
  344. function showImage(index) {
  345. // 更新图片位置
  346. imagesContainer.style.transform = `translateX(-${index * width}px)`
  347. // 更新指示器
  348. indicatorsContainer.querySelectorAll('button').forEach((btn, i) => {
  349. btn.style.backgroundColor = i === index ? '#000' : '#ccc'
  350. })
  351. currentImageIndex = index
  352. }
  353. function nextImage() {
  354. currentImageIndex = (currentImageIndex + 1) % maxImages
  355. showImage(currentImageIndex)
  356. }
  357. // 为指示器添加点击事件
  358. indicatorsContainer.addEventListener('click', function (e) {
  359. if (e.target.tagName === 'BUTTON') {
  360. const targetIndex = parseInt(e.target.dataset.target)
  361. showImage(targetIndex)
  362. }
  363. })
  364. // 自动播放轮播图
  365. let intervalId = setInterval(nextImage, interval)
  366. // 可选:鼠标悬停时停止自动播放
  367. carouselContainer.addEventListener('mouseenter', () => {
  368. clearInterval(intervalId)
  369. })
  370. carouselContainer.addEventListener('mouseleave', () => {
  371. intervalId = setInterval(nextImage, interval)
  372. })
  373. return carouselContainer
  374. }
  375. // 轮播End
  376. const script = document.querySelector('#sxtv-ad-id')
  377. const CFG =
  378. agreement + 'assets.qidian.sxtvs.com/ads/catalog/' + script.getAttribute('ad_id') + '.json'
  379. try {
  380. fetch(CFG + '?' + Date.now())
  381. .then((res) => res.json())
  382. .then((res) => {
  383. const data = res
  384. // 广告sdk
  385. if (localStorage.getItem('ad_id')) {
  386. ad_init(data)
  387. return
  388. }
  389. // 生成并输出浏览器指纹
  390. generateBrowserFingerprint().then((ad_id) => {
  391. localStorage.setItem('ad_id', ad_id + '')
  392. ad_init(data)
  393. })
  394. })
  395. } catch (error) {
  396. console.error(error)
  397. }
  398. })