前言

最近我们开发了A/B Testing 平台,开发web可视化实验中,涉及到页面在线编辑的实现,本文对此展开叙述。我在另一篇文章里也简单做了分享,有兴趣的可以点击查看abtest-可视化实验sdk编辑功能开发浅谈。

功能介绍

用户接入我们A/B Testing平台,可选择三种实验类型,可视化实验是其中一种便于用户快速上手的实验类型。简单来说,用户创建好可视化实验后,在目标页内针对元素属性进行实时编辑。实验开始后,sdk将拉取该配置进行渲染操作。
这里核心功能实现元素的编辑操作,有以下步骤:
1. 进入目标页后,获取当前实验版本的元素配置信息,同时将这些已配置的元素原版本信息保存起来,然后再根据配置信息渲染元素;
2. 加载编辑模块,分为:属性编辑模块和元素选择模块;
3. hover、select某个元素后,在该元素上绘制蒙层做标记;
4. select某个元素后,采集该元素属性信息,推送给属性操作模块;
5. 属性操作模块显示推送过来的属性值,每次编辑属性时,推送给元素选择模块进行实时渲染;
6. 属性编辑模块支持操作记录回退、前进、保存操作;

开发实现

接下来我们按照上个介绍的步骤,讲解下实际开发要点。

1. 编辑模式

当页面打开时,我们会在页面url上添加hubble_abtest_visual_key标志,然后sdk根据该标志确定进入编辑模式,依赖该标志拉取实验配置信息,以及加载编辑模块,
拉取的实验配置托管给编辑模块渲染。
由于我们平台当前不支持A/A 测试,所以若没有编辑,我们是不允许保存操作的。要实现这个判断,我们需要将元素配置前的信息和配置后信息做对比。故在配置渲染前,我们要保存下当前的元素信息。

2. 属性编辑模块

属性编辑模块,包含记录操作和元素属性编辑操作两大功能,这里就讲解下属性编辑功能实现要点。
当前我们支持编辑元素属性有:尺寸、文本、背景、边框、提示信息、目标链接。

配置信息数据结构

一个元素将配置如下信息:

{selector: "#analytics > a"css: {"background-image":"none","border-color":"rgba(0, 0, 0, 0.65)","border-style":"none","border-width":"0px","color":"rgba(0, 0, 0, 0.65)","display":"block","font-size":"14px","font-weight":"400","height":"42px","text-align":"start","visibility":"visible","width":"1920px"},attributes: {placeholder: "","href": "javascript:;"},nodeName: 'a'
}

selector 表示元素的选择器;css 表示元素的样式信息;attributes 表示元素的属性信息;nodeName 表示元素的标签类型;

尺寸
尺寸:元素的 width、height、显示、隐藏、删除。
其中 width 和 height 需要用户自己填入,显示操作是tab按钮选择,这些都属于css类设置。
比如 width 100px ,height 100px ,元素隐藏(占坑):

{css: {width: '100px',height: '100px',display: '';visibility: 'hidden'}
}

文本
文本: 元素的 color、font-size、font-weight、text-align。
文本的color,我们引入了一个颜色选择器,方便用户操作。
配置信息如下:

{css: {color: 'rgba(0, 0, 0, 0)',"font-size":"14px","font-weight":"400","text-align":"center"}
}

背景
背景: 元素的 background-image、background-color。
背景图片我们允许用户填入一个图片地址或者上传一张图片。
配置信息如下:

{css: {"background-color":"rgb(221, 221, 221)","background-image":"none"}
}

边框
边框:元素的 border-color、border-style、border-width。
配置信息如下:

{css: {"border-color":"rgba(0, 0, 0, 0.65)","border-style":"none","border-width":"0px"}
}

提示信息
提示信息,针对 input、textarea元素的 placeholder 属性。

配置信息如下:

{attributes: {placeholder: ""}
}

目标链接
目标链接,针对 a元素的 href 属性。

配置信息如下:

{attributes: {href: "###"}
}

上面我们介绍了编辑一个元素涉及的属性信息,这块确定后,我们接下来讲解下元素选择模块。

3. 元素选择模块

该模块包含选择元素和渲染属性两功能,下面主要讲解下元素选择功能。
一个页面,上面有各种可交互的元素,当我们在编辑时,应当禁止这些交互。实现这些方式有很多种,这里我们通过在body最底部覆盖一层蒙版实现。

// js
// 在body底部插入一个蒙层元素
const hubbleoverlay = document.createElement('hubbleoverlay');
hubbleoverlay.className = 'hubble-abtest-page-overlay';
document.body.append(hubbleoverlay);
/** css **/
.hubble-abtest-page-overlay {background: transparent;display: block;position: fixed;right: 0px;top: 0px;width: 100%;height: 100%;margin: 0 !important;padding: 0 !important;z-index: 2147483647 !important;}

上面的实现,将带来一个问题,无法正确选择页面元素了,下面我们都将这个问题为前提实现元素选择功能。

正确获取选择的元素
要获取元素,首先我们在body上绑定一个监听事件,同时禁止右键,避免干扰。

// js
const handleEvent = function(e) {e.preventDefault();e.stopPropagation();if (e.type === "contextmenu") {return false;}if (e.type === 'click') {handleClick(e);}
};
document.body.addEventListener("click", handleEvent);
document.body.addEventListener("contextmenu", handleEvent);

当点击元素时,触发了 handleClick 方法,此时我们获取到的 e.target 是 hubbleoverlay,这并非我们想要的。
这里我们将使用 document.elementFromPoint 方法,传入 e.clientX, e.clientY两个参数后,我们将获取当前文档上处于指定坐标位置最顶层的元素(点击了解该API)。

我们在 handleClick 方法内,首先将 hubbleoverlay蒙层宽度设置为0,然后调用 document.elementFromPoint方法,获取想要的元素,最后还原hubbleoverlay蒙层宽度。

// js
const getElementFromPoint = function(e) {const $overlay =  document.getElementsByClassName('hubble-abtest-page-overlay')[0];$overlay.style.width = '0';const $element = document.elementFromPoint(e.clientX, e.clientY);$overlay.style.width = '';return $element;
};
const handleClick = function(e) {const $element = this.getElementFromPoint(e);
};

元素上绘制选中状态
点击元素后,我们已经正确获取到元素对象,此时需要在元素上绘制一个蒙层,表示已选择了该元素。由于页面上已经被hubbleoverlay蒙层覆盖,我们绘制的元素蒙层层级需要比hubbleoverlay蒙层高,故我们在body底部再次新增一个元素,作为该元素的蒙层。

// js
const types = {'hover': 'hubble-abtest-hover','selected': 'hubble-abtest-selected'
};
const highlight = function(e) {const selector = e.selector || '';const $elArr = _.querySelectorAll(selector);for(let i = 0; i < $elArr.length; i += 1) {let $div = document.createElement('div');$div.className = 'hubble-abtest-cursor ' + types[e.type];document.body.insertBefore($div, _.querySelector('.hubble-abtest-page-overlay'));//设置元素蒙层的样式,width、height、left、top_.setOverlayPropertiesForElement($elArr[i], $div);}
};
const handleClick = function(e) {const $element = this.getElementFromPoint(e);const selector;highlight({selector: selector,type: 'selected'});
};
/**css**/
'.hubble-abtest-cursor {position: fixed; background-color: rgba(0, 107, 255, 0.21); border: 1px solid rgba(0, 107, 255, 1); z-index: 2147483647 !important;pointer-events: none;border-radius: 2px;box-sizing: content-box;margin: 0 !important;padding: 0 !important; }'

设置元素蒙层样式
上面我们已经在页面上添加了选中元素的蒙层div了,但是由于setOverlayPropertiesForElement未实现,该div并不能正确定位到元素上,接下来我们讲解下该方法的实现。
setOverlayPropertiesForElement(originEl, targetEl)方法,对应的参数说明下,originEl 表示选中的元素对象,targetEl表示元素蒙层对象,其实就是我们通过获取选中元素的属性,来确定元素蒙层的位置。
要确定元素蒙层的位置,我们需要确定该蒙层的 width、height、left、top这四个属性。
首先我们要获取选中元素的 top、left、width、height。
获取选中元素的 top、left 方法 offset:

const _ = {};
_.offset = function(itemEl) {if (!itemEl) return;const rect = itemEl.getBoundingClientRect();if ( rect.width || rect.height ) {const doc = itemEl.ownerDocument;const docElem = doc.documentElement;// 兼容IE写法 ==》  - docElem.clientTop、 - docElem.clientLeft ,其它浏览器为0px,IE为2pxreturn {top: rect.top + window.pageYOffset - docElem.clientTop,left: rect.left + window.pageXOffset - docElem.clientLeft};}else{return {top: 0,left: 0}}
};

获取选中元素的 width、heigt 方法 getSize:

_.getSize = function(itemEl) {if (!itemEl) return;if (!window.getComputedStyle) {return {width: itemEl.offsetWidth, height: itemEl.offsetHeight};}try {const bounds = itemEl.getBoundingClientRect();return {width: bounds.width, height: bounds.height};} catch (e){return {width: 0, height: 0};}
};

获取页面left top方向间距 getElementSpacingOffset:

_.getElementSpacingOffset = function(direction) {const $html = document.getElementsByTagName('html')[0];const $body = document.getElementsByTagName('body')[0];const scroll = (direction === 'top' ? window.scrollY : window.scrollX);const htmlPadding = parseInt(this.getStyle($html, 'padding-' + direction));const htmlMargin = parseInt(this.getStyle($html, 'margin-' + direction));const htmlBorder = parseInt(this.getStyle($html, 'border-' + direction));const bodyBorder = parseInt(this.getStyle($body, 'border-' + direction));let a = 0;if (htmlBorder > 0 && bodyBorder > 0) {a = htmlBorder + bodyBorder;}return parseInt(htmlPadding + htmlMargin + scroll + a, 10);
};

然后选中设置蒙层的样式实现 setOverlayPropertiesForElement:

_.setOverlayPropertiesForElement = function(originEl, targetEl) {const offset = this.offset(originEl);const getSize = this.getSize(originEl);const elementBounds = {bottom: offset.top + getSize.height,top: offset.top,left: offset.left,right: offset.left + getSize.width,width: getSize.width,height: getSize.height};const setOverlayPropertiesForElementLeft = this.getElementSpacingOffset('left') + 1;const setOverlayPropertiesForElementTop = this.getElementSpacingOffset('top') + 1;targetEl.style.top = (elementBounds.top - setOverlayPropertiesForElementTop)  + 'px';targetEl.style.left = (elementBounds.left - setOverlayPropertiesForElementLeft)  + 'px';targetEl.style.width = elementBounds.width + 'px';targetEl.style.height = elementBounds.height + 'px';
};

这里要说明下:若选中元素单位为em,rem这些,可能存在问题,本文到此并未实际测试。

设置元素蒙层样式是个非常重要的实现,故本文贴出详细代码供大家参考。

获取选中元素信息
上面我们已经获取到选中的元素,同时也加了选中标志。之前已讲解过,我们还需提取选中元素的属性信息,这些属性信息请参考编辑模块那节。
首先我们继续在 handleClick方法内,触发信息trigger。

// js
const handleClick = function(e) {//省略....//获取元素选择器const $element = getElementFromPoint(e);const selector = _.getDomSelector($element);//省略....// 获取元素信息const elementInfo = _.getElementInfo($element);_.trigger('selected',{selector: selector,css: elementInfo.css,attributes: elementInfo.attributes,nodeName: elementInfo.nodeName});
};

接下来讲解下 getElementInfo 方法的实现。
为了获取选中元素的css样式,我们实现了 getStyle方法,我们知道,用document.getElementById('element').style.xxx 可以获取元素的样式信息,可是它获取的只是DOM元素style属性里的样式规则,对于通过class属性引用的外部样式表,就拿不到我们要的信息了。所以我们实现如下:

_.getStyle = function(itemEl, cssKey) {// 兼容IEif(itemEl.currentStyle){return itemEl.currentStyle[cssKey];}else{return itemEl.ownerDocument.defaultView.getComputedStyle(itemEl, null).getPropertyValue(cssKey);}
};

解决了获取元素css样式问题,getElementInfo 方法就好实现了:

_.getElementInfo = function(itemEl) {if (!itemEl) {return null;}const obj = {nodeName: itemEl.nodeName,html: itemEl.innerHTML,outerHtml: itemEl.outerHtml,css: {'width': _.getStyle(itemEl, 'width'),//省略...'border-width': _.getStyle(itemEl, 'border-width')},attributes: {}};if (itemEl.nodeName === 'A') {obj.attributes.href = itemEl.getAttribute('href');}if (itemEl.nodeName === 'INPUT' || itemEl.nodeName === 'TEXTAREA') {obj.attributes.placeholder = itemEl.getAttribute('placeholder');}return obj;
};

页面滚动和缩放
当我们页面滚动和缩放时,所绘制的元素蒙版会出现定位错乱情况,此时解决方式就是调用setOverlayPropertiesForElement 方法重新设置样式。但该方法需要拿到当前选中的元素,故每次选中元素时,我们就保存该元素,这里要特别注明:整个操作中,当前只能有一个被选中的元素。若可选择多个,考虑的可能是蒙层和元素之间的一一对应关系了,本文无需考虑。

// js
const selectedElement = null;
const rerender = function() {const $hubbleAbtestCursorSelected = _.querySelector('div.hubble-abtest-selected');if ($hubbleAbtestCursorSelected) {_.setOverlayPropertiesForElement(this.selectedElement, $hubbleAbtestCursorSelected);}
};
window.addEventListener('resize', () => {setTimeout(() => rerender(), 50);
});
window.addEventListener('scroll', () => {setTimeout(() => rerender(), 50);
});

上面我们延迟了50ms重设,只是简单确保页面变动已完毕。

鼠标hover
当然我们每次鼠标hover一个元素时,也会绘制元素蒙层,该实现方式跟选中元素基本一致,本文不再讲解了。

结尾

本文简单介绍了A/B Testing平台的可视化实验一些功能,讲解了实现页面编辑功能的技术要点,希望对大家有帮助。
@作者:白云飘飘(534591395@qq.com)

@github: https://github.com/534591395

欢迎关注我的微信公众号:
或者微信公众号搜索 新梦想兔,关注我哦。

web在线页面编辑实现-abtest可视化实验相关推荐

  1. 免费抠图换背景软件分享,在线页面随意编辑

    随着如今社交应用的发展,很多小伙伴们应该都有在不同平台发布图文分享吧.对于好的内容创作来说不仅要有好的文案,还需要配上好看的图片.抠图更换背景就成了很多博主处理图片中操作的一环,那么也就需要选择一些适 ...

  2. 基于web的在线视频编辑的设计

    前言 在这里,先吐槽一下,最近一直很忙,就要过年了,公司项目赶得要命,吃不好,睡不好,周末都没得休息(写到这里就憋着一肚火了).不过,付出还是有回报的,在团队的合作努力下,项目还是在过年前完成了,最近 ...

  3. 可视化展示——web展示页面

    web展示页面 展示界面 展示界面 1.这个界面用来选择论文集合,在论文集合选择完成之后,分析出论文集合的一些性质,比如平均reference引用数目,平均字数等等论文集合的特征,并用柱状图等形象化表 ...

  4. 支持chrome edge谷歌浏览器在线WEB 网页页面 打印 条形码

    appemit   支持chrome edge谷歌浏览器在线WEB 网页页面 打印 条形码 直接在js来操作dll,控制打印的相关参数. 网站 http://www.appemit.com Lodop ...

  5. web视频剪辑 在线视频编辑 开发类似:VE视频引擎 美摄sdk VESDK 蓝松短视频SDK 筷子saas剪辑 系统源码

    在线视频编辑 剪辑系统源码 可以批量视频制作 包括ae特效制作**(支持所有AE底层能力)** web视频剪辑 在线视频编辑 VE视频引擎 美摄sdk VE SDK 蓝松短视频SDK 剪辑系统源码 看 ...

  6. java 协作编辑,在线协作编辑器之周报收集

    在线协作编辑器之周报收集 一.实验说明 下述介绍为实验楼默认环境,如果您使用的是定制环境,请修改成您自己的环境介绍. 1. 环境登录 无需密码自动登录,系统用户名shiyanlou 2. 环境介绍 本 ...

  7. linux web 共享文件夹,KodExplorer 3.2外链支持,文件夹共享,Web在线文件管理

    KodExplorer 3.2外链支持,文件夹共享,Web在线文件管理 发布时间:2015-10-26 09:09:53来源:红联作者:empast KodExplorer是款开源的Web在线文件管理 ...

  8. View Components as Tag Helpers,离在线模板编辑又进一步

    在asp.net core mvc中增加了ViewComponent(视图组件)的概念,视图组件有点类似部分视图,但是比部分视图功能更加强大,它更有点像一个控制器. 使用方法 1,定义类派生自View ...

  9. ACE editor 在线代码编辑极其高亮

    简介 ACE是一个开源的.独立的.基于浏览器的代码编辑器,可以嵌入到任何WEB页面或者JavaScript应用程序中,ACE支持超过60种语言语法高亮,并能够处理代码多达400万行的大型文档. 特性 ...

最新文章

  1. ubuntu14 备份
  2. pytorch维度统计
  3. POJ 2253 Frogger(最短路 Floyd)
  4. 同时买票是怎么实现的_刷脸进站,语音买票!广州地铁这波操作666~
  5. 【译】An Intro to TrueBit: A Scalable, Decentralized Computational Court.
  6. 有关 Oracle redo log
  7. Effective C# 原则34:创建大容量的Web API(译)
  8. Java面试题及答案,mysql类型
  9. mysql key_mysql 索引 key 的用法
  10. 高能同步辐射光源科学数据管理策略研究与应用
  11. regexbuddy使用记录
  12. 网上图书商城项目学习笔记-008修改密码功能
  13. iphonex适配游戏_Unity+iPhoneX适配方案
  14. 【报告分享】快手私域经营白皮书-磁力引擎(附下载)
  15. 计算机基础知识--->对张海藩老师所著《软件工程》的这本书的一些知识总结
  16. Linux:命令gedit主要作用是什么?
  17. 小白版-实现网页APP抢券按钮的自动点击?
  18. 远程修改ESXi 6.7管理IP地址
  19. 货物贸易外汇监测系统 企业版_重点耗能企业能耗监测计量系统,能源管控平台方案...
  20. 在photoshop下安装Imagenomic_Portraiture(磨皮插件)图解全过程

热门文章

  1. 不奋发,则心日颓靡;不检束,则心日恣肆。
  2. PotPlayer技巧总结
  3. 每日点滴之贰零壹壹年拾壹月贰拾叁日-莫名其妙缺少的磁盘空间
  4. 找出规律快速实现双螺旋矩阵
  5. android 内凹的圆角,css实现内凹圆角样式
  6. 家庭财务管理软件c语言,家庭财务管理软件
  7. 提高雅思听力成绩必须培养的能力
  8. PyGame游戏制作: 弹球游戏Pong(附上Python完整代码)
  9. 使用Squirrel连接Phoenix
  10. 大数据之Phoenix和Squirrel