国内很火的一款WordPress主题Sakura(演示地址、作者博客:https://2heng.xin/),它的回到顶部功能是由一个手动拉的挂件装饰实现的(右下角),取代了传统网页中简单的“回到顶部”按钮,在追求个性化的个人blog中,这就很吸引人。

受到这种设计的启发,我琢磨了一个挂件出来:

simple toolbar widget
位于页面右下角的简单小工具挂件

上面的实际效果图中,左边四个按钮分别为四个功能:更换壁纸为今日Bing美图、播放自定义背景音乐、回到顶部、关闭挂件菜单。右边圆形中的图案除了装饰作用外,还充当加载等待动画的功能,当加载Bing图片时候会旋转,表示正在进行网络请求;而后面的淡黄色太阳图案则表明时间,白天时候是☀,晚上是星星图案。

开发小记

外观设计

首先要实现的就是这种悬挂的效果,我用了三段div拼接,分别为:悬挂线、连接点、末端圆形。对这三段进行定位操作,这中间可能会用到之前写过的CSS3自定义属性(也可能不会用到,如果有需要动态改变挂件大小或者位置的话就会用到),以及CSS函数var()(该函数用来获取某一自定义属性的值)和calc()(该函数用来声明有多个单位参与运算时的属性值,比如pxvw组合使用来确定一个元素的宽度)。

最外层元素CSS样式声明:

div[id^="customize-toolbar"] {
	transition: bottom 1s linear;
	position: fixed;
	width: 200px;
	height: 200px;
	bottom: calc(var(--tb-bottom));
	right: -2px;
	opacity: 1;
	z-index: 105;
	--tb-bottom: 2px;
	--tb-dot-bottom: 160px;
}

在最外层的div上定义了两个自定义属性--tb-bottom--tb-dot-bottom,分别代表挂件整体距离窗口底部的距离连接点距离挂件底部的距离。在垂直方向上对挂件位置进行操作会用到前一个属性。

控制悬挂线的CSS样式声明:

.customize-toolbar-topline {
	width: 2px;
	height: calc(100vh - var(--tb-bottom) - 160px);
	background: #000;
	position: absolute;
	bottom: var(--tb-dot-bottom);
	left: 100px;
	transition: height 1s linear;
}

在以上样式中,悬挂线的height属性采用计算的形式确定,窗口总高度减去上述两个加粗的距离得到悬挂线总长度。

在最新的版本中添加了可以手动收起/展开挂件的功能,就是通过控制--tb-bottom属性值实现的,当该属性值发生变化时,所有引用该属性的元素都会发生一些变化:

  • 最外层元素,即整个挂件,它的bottom属性值会变化,这会使它在垂直方向移动
  • 悬挂线的height属性值会变化,这会产生有一根线把挂件拉上去或者放下来的效果

控制--tb-bottom属性值,可以直接给最外层元素添加不同的CSS类,而不是用JavaScript一直修改它,配合transition属性产生过渡效果,transition属性中规定的时长决定了整个运动的时长。

div[id^="customize-toolbar"].customize-toolbar-all-hide {
	--tb-bottom: calc(100vh - 20px);
}

这边还有一点要考虑,就是挂件往上收起的时候,最后要露出一点(以上样式声明中留出了20px的高度),可以让人点击,再把它拉下来。


其次就是按钮的设计了,之前琢磨过很多按钮,对于按钮的设计我的要求是按钮要有手感,不论鼠标悬浮还是点击都要有反馈(关于点击反馈可以体验一下Google家的网站以及APP),不要像百度那样干巴巴的,鼠标点上去跟没点一样(百度老早就不用了,不知道现在怎么样了,不过以前的体验确实很糟糕)。

对于按钮,一个是鼠标悬浮,一个是鼠标点击,这两种情况时候可以添加一些效果。悬浮时候更改一下按钮配色或者阴影。点击时候分鼠标按下去和鼠标松开两种情况,这两种情况我都做了处理,鼠标按下跟鼠标松开时候,按钮中间图案的阴影方向不一样,表示是按下了还是松开了。

//实现鼠标按下和松开时的外观切换
b.addEventListener("mousedown", function () {
	b.classList.toggle(t.c.v.iconClick);
});
b.addEventListener("mouseup", function () {
	b.classList.toggle(t.c.v.iconClick);
});

通过给dom绑定mousedownmouseup事件,结合dom.classList.toggle方法,实现在两种CSS类中来回切换。


第三个就是切换背景图时候的过渡动画,生硬地直接换背景肯定是不友好的,我想了个平移效果:换新背景时候,当前图片会从左往右移走,新图片会从右往左平移过来;换回原来背景的时候,新背景图会从左往右移回去,原来图片再从右往左移回来。

背景图的平移,本质上是操作background-position属性,更改图片位置,再结合transition属性的过渡动画,就能实现把图片移走或者移来的效果。

背景图片的两个位置,平移走后的位置和正常位置:

body.bingwallpaper-bg-start {
	background-position-x: 100vw;
}
body.bingwallpaper-bg-end {
	background-position-x: center;
}
//实现背景图平移滑入滑出的效果
var temp = document.createElement("img"),
	_this = this;
temp.setAttribute("src", u);
temp.addEventListener("load", function () {
	var b = document.body;
	if (!b.style.transition) {
		b.style.transition = "background-position-x 2s linear";
	}
	b.classList.add("bingwallpaper-bg-start");
	setTimeout(function () {
		b.classList.add("bingwallpaper-bg-end");
		b.style.backgroundImage = `url(${u})`;
        }, 2000);
});

在做完当前这张图片的位置移动后(添加bingwallpaper-bg-start类),背景图就会移走,从开始移动到完全移走的用时取决于transition属性的值,然后隔一小段时间,比如2秒(间隔的时间与transition属性值要一致),再操作下一张图片。在这里虽然是两张图,但本质上只有一个图片容器body,所以在2秒后,需要更改body的背景图,然后添加bingwallpaper-bg-end类,让图片再移回来。这样移走需要2秒,移回来也是2秒,中间没有间隔,就能创造出连续的效果。

换回原来背景图片的原理还是一样的,依然是操作这两个CSS类以及body的背景图,发生变化的是操作的顺序。

//换回原来背景图
var b = document.body,
	_this = this;
b.classList.remove("bingwallpaper-bg-end");
setTimeout(function () {
	b.style.removeProperty("background-image");
	b.classList.remove("bingwallpaper-bg-start");
}, 2000);

写CSS时候一个地方得特别注意:当元素具有border-width属性时,要看清盒子模型啊啊啊~~~~,标准盒子和怪异盒子对这个属性的处理不一样的,标准盒子(box-sizing属性值为content-box)元素高度宽度不包括border-width,而怪异盒子(box-sizing属性值为border-box)元素是包括的。在对元素进行精确位置定位时,忘记考虑这点的话会很难受。

功能实现

在上面更换背景图的代码中,我用了先加载img图片再加载背景图的方法:

var temp = document.createElement("img"),
	_this = this;
temp.setAttribute("src", u);
temp.addEventListener("load", function () {
	//img图片加载完后,执行...
});

这样的好处是避免背景图加载过程中显示大片空白影响体验,而img元素可以监听load事件,在图片加载完成后再把它用作背景图(利用浏览器缓存,浏览器可以直接利用加载好的图片而不是重新下载它),在加载过程中可以什么都不显示出来(这个临时创建的img元素不能添加到dom树上或者添加了但是要声明display:none)。


获取Bing图片时候,由于Bing服务器没有在响应头上设置access-control-allow-origin允许其它域访问,所以不能从前端浏览器直接发起请求,需要一个后端服务器来代替发起请求,然后浏览器只要向这个后端服务器请求就行。

一方面不会PHP,另外一方面为了不给兔子服务器添加压力,这个后端服务器我采用了Cloudflare Workers来实现,然后利用路由自定义功能将Workers Url添加到我自己的博客域名上,就能避免跨域请求了。

只需要一些JavaScript和HTTP编程知识就能编写简单的Workers代码,这是一个完全由JavaScript驱动的后端服务器,只要编写功能代码,无需操心各种环境配置,非常方便。


不是关键的地方就不写了,最后就是整体封装,各个功能分拆成独立的方法、属性,组合成一个完整的对象。在ES6语法里有class语法糖可以用,先声明类,再实例化类的对象。

使用方法

我准备了一个自定义的选项,允许使用不同的颜色、图片、背景音乐来初始化小工具对象:

const toolbar_opt = {
	toolbarTheme: {
		//按钮字体颜色
		"font-color": "coral",
		//按钮背景颜色
		"bg-color": "beige",
		//按钮阴影颜色
		"border-shadow-color": "lightcoral",
		//按钮鼠标悬浮颜色
		"bg-hover-color": "lightpink",
		//按钮图案
		"bg-img-css": "url(*.jpg)",
	},
	bgm: {
		//背景音乐文件地址
		url: "",
		//背景音乐长度(暂时没用,以后可能会加播放动画)
		length: 0,
	},
	//移动设备可见性
	mobileVisibility: 0,
};

移动设备可见性设置里,0表示移动设备上不加载,注意是不加载哦,不是简单的CSS里加一条display:none,是直接不渲染DOM,减少性能开支;1表示移动设备上也加载,但是不推荐,会占用屏幕空间。

不填写自定义信息的话,就跟文章开头图片里的一模一样,但是没有背景音乐。完整的调用是这样:

<script src="https://cdn.jsdelivr.net/gh/littlestar520521/wordpress-tool-package/publish/combine/dist/toolbar-uni.min.js"></script>
<script>
	const toolbar_opt = {
		toolbarTheme: {
			"font-color": "",
			"bg-color": "",
			"border-shadow-color": "",
			"bg-hover-color": "",
			"bg-img-css": "url()",
		},
		bgm: {
			url: "",
			length: 0,
		},
		mobileVisibility: 0,
	};
	var x = new ToolBar(toolbar_opt);
	x.init();
</script>

GitHub仓库地址:传送门

发表评论

电子邮件地址不会被公开。 必填项已用*标注