功能实现
实现效果一
效果
配置
// 首屏引导
function loadHomeLead(){
// 是否已经加载过了
const is = localStorage.getItem('loadHomeLead')
if(is) return
const step1pos = document.querySelectorAll('.info')[1].getBoundingClientRect()
const config = {
done(){
localStorage.setItem('loadHomeLead', 'true')
},
steps:[
{
top: step1pos.top,
left: step1pos.left,
width: step1pos.width,
height: 66,
// 文本区域
text:{
value:'同一地区存在多个项目时会合并显示<br/>点击显示所有合并的项目',
className:'', // 自定义类 控制样式
},
// 按钮
btn:{
next:{text:'我知道了'},
// prev:{text:'上一步'},
// done:{text:'结束'},
},
end(nextIndex){
console.log('第一步移动完成')
}
},
{
height: 66,
start(index){
return new Promise((resolve, reject)=>{
map.on("mapmove",move)
function move(){
map.off("mapmove",move)
setTimeout(()=>{
const steppos = document.querySelectorAll('.info')[0].getBoundingClientRect()
config.steps[index].top = steppos.top
config.steps[index].left = steppos.left
config.steps[index].width = steppos.width
resolve()
}, 100);
}
// 展开地图
map.setZoomAndCenter(14, [120.6522,27.75407],)
})
},
text:{
value:'项目图标,点击查看项目详情',
className:'',
},
btn:{
next:{text:'我知道了'},
},
},
{
start(index){
const steppos = document.querySelectorAll('.search')[0].getBoundingClientRect()
config.steps[index].top = steppos.top
config.steps[index].left = steppos.left
config.steps[index].width = steppos.width
config.steps[index].height = steppos.height
},
text:{
value:'点击搜索按钮输入项目名称可以搜索定位项目',
className:'',
},
btn:{
next:{text:'我知道了'},
},
},
{
start(index){
const steppos = document.querySelectorAll('.layers')[0].getBoundingClientRect()
config.steps[index].top = steppos.top
config.steps[index].left = steppos.left
config.steps[index].width = steppos.width
config.steps[index].height = steppos.height
},
text:{
value:'点击可以切换地图的样式',
className:'',
},
btn:{
next:{text:'我知道了'},
},
}
]
}
const lead = new Lead(config)
lead.start()
}
实现效果二
效果
…
配置
// 项目详情引导
function projectDetailsLead(){
const is = localStorage.getItem('DetailsLead')
if(is) return;
function getPos(obj, {top, left, width, height}, offset={top:0, left:0, width:0, height:0}){
obj.top = top + offset.top
obj.left = left + offset.left
obj.width = width + offset.width
obj.height = height + offset.height
}
const config = {
done(){
localStorage.setItem('DetailsLead', 'true')
},
steps:[
{
start(index){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
const steppos = document.querySelectorAll('.navigation')[0].getBoundingClientRect()
getPos(config.steps[index], steppos, {
top: -5, left: -5, width:10, height: 5
})
resolve()
}, 200)
})
},
text:{
value:'导航按钮:点击可以导航到项目所在位置',
className:'', // 自定义类 控制样式
},
btn:{
next:{text:'我知道了'},
},
},
{
start(index){
const steppos = document.querySelectorAll('.btns>.list')[0].getBoundingClientRect()
getPos(config.steps[index], steppos, {
top: 0, left: -5, width:10, height: 0
})
},
text:{
value:'点击显示该项目所有时间段的项目列表,用户可以选择<br/>想要了解的时间段查看项目进展,再次点击隐藏列表',
className:'', // 自定义类 控制样式
},
btn:{
next:{text:'我知道了'},
},
},
{
start(index){
const steppos = document.querySelectorAll('.btns>.btn_full')[0].getBoundingClientRect()
getPos(config.steps[index], steppos, {
top: 0, left: -5, width:10, height: 0
})
},
text:{
value:'点击该按钮可以把全景图分屏进行对比,配合列表按钮<br/>上下两屏选择不同时间点的项目,可以清楚了解项目的<br/>进展情况,再次点击取消分屏',
className:'', // 自定义类 控制样式
},
btn:{
next:{text:'我知道了'},
},
},
{
start(index){
const steppos = document.querySelectorAll('.btns>.btn_sync')[0].getBoundingClientRect()
getPos(config.steps[index], steppos, {
top: 0, left: -5, width:10, height: 0
})
},
text:{
value:'分屏按钮是配合分屏使用的,可以让分屏时的两个全景<br/>图在滑动、放大等操作时是否同步操作的设置',
className:'', // 自定义类 控制样式
},
btn:{
next:{text:'我知道了'},
},
},
{
start(index){
const steppos = document.querySelectorAll('.btns>.btn_des')[0].getBoundingClientRect()
getPos(config.steps[index], steppos, {
top: 0, left: -5, width:10, height: 5
})
},
text:{
value:'点击查看该项目的详细信息',
className:'', // 自定义类 控制样式
},
btn:{
next:{text:'我知道了'},
},
}
]
}
const lead = new Lead(config)
lead.start()
}
实现过程
lead.js
class Lead{
constructor({steps=[], done=()=>{}}){
this.moveIndex = -1;// 当前进度
this.container = document.body; // 挂载区域
this.steps = steps
this.doneFun = done
}
start(){
// 容器
this.el = this.createDom('div','lead-container')
// 高亮
this.highlight = this.createDom('div','lead-highlight')
this.highlight.appendChild(this.createDom('div','lead-highlight-border'))
// 信息区
this.contextdom = this.createDom('div','lead-context')
// 文本区
this.textdom = this.createDom('div','lead-text')
// 按钮区
this.btnDom = this.createDom('div','lead-btns')
this.btnDom.btnChild = {}
// 指向区
this.oriented = this.createDom('div','lead-oriented')
this.el.appendChild(this.highlight)
this.el.appendChild(this.contextdom)
this.container.appendChild(this.el)
// 第一次聚焦,高亮
if(this.steps.length){
this.next()
this.showTextDom()
this.steps[0].end && this.steps[0].end(this.moveIndex)
}
// 添加过渡
this.highlight.style.transition = 'all .2s'
}
next(){
this.moveIndex ++;
if(this.moveIndex >= this.steps.length){
return this.done()
}
this.step()
}
prev(){
if(this.moveIndex <= 0){
return this.done()
}
this.moveIndex --;
this.step()
}
done(){ // 结束
this.el.remove()
this.doneFun()
}
async step(){
const currConfig = this.steps[this.moveIndex]
if(currConfig.start){
await currConfig.start(this.moveIndex)
}
this.hideTextDom()
console.log('step',this.moveIndex, currConfig)
this.highlightMove(currConfig)
this.moveTextDom(currConfig)
this.highlight.addEventListener("transitionend",()=>{
setTimeout(()=>{this.showTextDom()},100)
currConfig.end && currConfig.end(this.moveIndex)
},{once:true})
}
moveTextDom({left, top, height, width, text, btn}){ // 文本区移动
const {w, h} = this.getScreen();
const bottom = h - height - top;
const direction = top > bottom ? 'top' : 'bottom';
const orientedW = this.oriented.clientWidth || 64
if(text){
this.textdom.display = 'block'
const {value, className} = text
this.textdom.innerHTML = value
className && this.textdom.classList.add(className)
}else{
this.textdom.display = 'none'
}
if(btn){
Object.keys(this.btnDom.btnChild).forEach((key)=>{
this.btnDom.btnChild[key].style.display = 'none'
})
Object.keys(btn).reverse().forEach((key)=>{
if(this.btnDom.btnChild[key]){
this.btnDom.btnChild[key].innerText = btn[key].text
this.btnDom.btnChild[key].style.display = 'block'
}else{
let _btn = this.createDom('div', `lead_btn btn_${key}`)
_btn.innerText = btn[key].text
_btn.addEventListener('click',()=>this[key]())
this.btnDom.btnChild[key] = _btn
this.btnDom.appendChild(_btn)
}
});
this.btnDom.display = 'flex'
}else{
this.btnDom.display = 'none'
}
const pos = this.getPos({left, top, height, width})
this.oriented.style.transform = `rotate(${pos * 90}deg)`;
let orientedLeft = left + width/2;
if(orientedLeft + orientedW >= w || orientedLeft > w/2){
orientedLeft -= orientedW;
}
this.oriented.style.marginLeft = orientedLeft + 'px';
if(direction === 'top'){
this.contextdom.style.top = 'auto'
this.contextdom.style.bottom = bottom + height + 10 + 'px'
this.contextdom.appendChild(this.textdom)
this.contextdom.appendChild(this.btnDom)
this.btnDom.style.marginBottom = '-10px'
this.contextdom.appendChild(this.oriented)
}
if(direction === 'bottom'){
this.contextdom.style.top = top + height + 10 + 'px'
this.contextdom.style.bottom = 'auto'
this.contextdom.appendChild(this.oriented)
this.contextdom.appendChild(this.textdom)
this.btnDom.style.marginBottom = '0px'
this.contextdom.appendChild(this.btnDom)
}
}
getScreen(){
return {
h: this.container.clientHeight,
w: this.container.clientWidth
}
}
getPos({left, top, height, width}){ // 返回位置 0 左上角 1右上角 2左下角 3右下角
const {w, h} = this.getScreen()
let _left = w/2 >= left + width/2,
_top = h/2 >= top + height/2,
result = null
if(_left && _top) result = 0
if(!_left && _top) result = 1
if(!_left && !_top) result = 2
if(_left && !_top) result = 3
return result
}
hideTextDom(){ // 隐藏文本区
this.contextdom.style.transition = 'none';
this.contextdom.style.height = 0;
}
showTextDom(){ // 显示文本区
this.contextdom.style.transition = 'height 0.2s'
this.contextdom.style.height = 'auto';
}
createDom(tag, className){
const TAG = document.createElement(tag)
TAG.className = className
return TAG
}
highlightMove({left, top, height, width}){
const {w, h} = this.getScreen()
top = top <= 0 ? 0 : top
left = left <= 0 ? 0 : left
let right = w - left - width
let bottom = h - height - top
bottom = bottom <= 0 ? 0 : bottom
right = right <= 0 ? 0 : right
this.highlight.style.width = width + 'px'
this.highlight.style.height = height + 'px'
this.highlight.style.borderWidth = `${top}px ${right}px ${bottom}px ${left}px`;
// this.highlight.style.borderTopWidth = top + 'px'
// this.highlight.style.borderRightWidth = right + 'px'
// this.highlight.style.borderBottomWidth = bottom + 'px'
// this.highlight.style.borderLeftWidth = left + 'px'
window.highlight = this.highlight
}
}
lead.css
.lead-container{
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 99999;
}
.lead-container .lead-context,
.lead-container .lead-highlight{
position: absolute;
}
.lead-container .lead-highlight{
border-color: rgba(0, 0, 0, 0.49);
border-style: solid;
}
.lead-container .lead-highlight .lead-highlight-border{
border: 1px solid gray;
width: calc(100% + 2px);
height: calc(100% + 2px);
margin-left: -1px;
margin-top: -1px;
border-radius: 3px;
box-sizing: border-box;
}
.lead-container .lead-text{
color: #fff;
font-size: 12px;
margin: 10px;
text-align: center;
}
.lead-container .lead-context{
margin: auto;
width: 100vw;
/* transition: all 0.2s; */
/* outline: 1px solid red; */
overflow: hidden;
}
.lead-container .lead-oriented{
width: 64px;
height: 50px;
background: url(../img/lead.svg);
}
.lead-container .lead-btns{
display: flex;
justify-content: center;
margin-top: 10px;
/* margin-bottom: -10px; */
}
.lead-container .lead-btns .lead_btn{
color: #fff;
background: rgba(52, 98, 255, 1);
font-size: 12px;
border-radius: 20px;
margin-left: 10px;
padding: 4px 14px;
}
图片素材