微信小程式-基於高德地圖API實現天氣元件(動態效果)

Kindear發表於2020-10-21

微信小程式-基於高德地圖API實現天氣元件(動態效果)

​ 在社群翻騰了許久,沒有找到合適的天氣外掛。迫不得已,只好借鑑網際網路上的web專案,手動遷移到小程式中使用。現在分享到網際網路社群中,幫助後續有需要的開發者。

1.元件介紹

1.1 元件效果預覽圖

​ 小程式元件繼承了外部樣式colorui的色彩,但實際動畫會根據父節點的color屬性自動填充顏色,即使不引入colorui這個樣式庫,也可以在該元件引用外定義一個有color屬性的塊包裹該元件,同樣可以達到如圖的效果。

1.2 構造形式

1.3 支援的動畫效果

簡單介紹下,動畫由3個部分組成

一個是主體塊,這幾個動畫中的大雲朵就是;第二個是背景塊,如第一個中的太陽和第三個中的多層雲;第三個就是狀態塊,如第一個中的雨水和第二個中的雷。每個塊有且僅能展示一個。可以根據自己的需要,自行組合這幾個塊,來滿足對應的天氣需求。

:如想要實現雷雨交加的效果,需要定義兩個動畫,一個是雷一個是雨,然後通過定時器進行動畫的來回切換,如果有完成的可以在評論裡留下程式碼,我懶得實現了,哈哈。

2.元件的使用

元件的使用,需要授權獲取位置資訊,在app.json中配置授權。

"permission": {
    "scope.userLocation": {
      "desc": "你的位置資訊將用於定位效果和天氣資訊展示"
    }
  }

元件配置完成後,在全域性app.json中進行引入。

"usingComponents": {
    "uweather":"animation/uweather/weather"
 }

元件有兩種模式:

  1. 使用者自定義模式
  2. 預設模式(引入amap-wx.js,申請高德地圖key,具體步驟參見參考文件第一個)

使用者自定義模式下,所有的資訊包括動畫和資訊展示,都由使用者傳入的資訊來控制。

預設模式下,即使用者未傳入任何資訊,這時候元件就會基於位置資訊,請求高德地圖對應介面來獲取地理位置及其天氣資訊。

元件在被建立的時候會檢測是否有對應值的傳入,如果有值傳入,那麼就是使用者自定義模式,如果沒有值傳入,那麼就是預設模式。

lifetimes:{
    attached(){
      if(this.properties.winfo == null){
        this.setData({
          amapPlugin: new amap.AMapWX({
            key: this.data.key
          })
        },()=>{
            //獲取天氣資訊
          this.getWeather()
        })
      }
    }
  },

2.1 自定義模式

自定義模式下,傳入的資料要按照規定的的格式(也可以自己修改元件的屬性值)

例如在page中配置的屬性如下

    weather:'雷',
    winfo:{
      province:'自定義省份',
      city:'自定義城市',
      temperature:'自定義溫度',
      weather:'自定義天氣',
      winddirection:'自定義風向',
      windpower:'自定義風力'
    }

wxml頁面中的元件使用如下

<uweather
	weather="{{weather}}"
	winfo="{{winfo}}"
>
</uweather>

那麼對應的元件展示效果就是這樣子的

2.2 預設模式

​ 預設模式需要獲得使用者的地理位置資訊授權,確認在app.json中進行了授權配置和使用元件前完成了授權資訊的校驗。

​ 元件生命週期會在每一次元件被裝如頁面樹時,監聽是否有對應資料的傳入,如果沒有,就會請求對應的介面,獲取地圖資訊。使用預設方法,還需要配置 https://restapi.amap.com 為合法的request域名和申請對應的key用於開發,申請步驟參見參考文件。

​ 預設模式下不需要傳入任何引數,直接引入元件即可。

<uweather> </uweather>

3.元件使用注意事項

​ 預設方法的天氣返回值具有很多種,具體使用還需要自己修改元件,完成不同天氣到對應動畫的對映,例如小雨、中雨、大雨都可以對映到這個動畫狀態。下圖是高德地圖天氣API的部分資訊,全部請參見參考文件。

4.元件程式碼

​ 詳細的元件在專案中的使用結構 請看[開源專案](miniprogram/animation/uweather · Kindear/校園小程式 - 碼雲 - 開源中國 (gitee.com)),記得給個⭐,感謝。

uweather.js

// animation/uweather/rain.js
const amap = require('../../lib/amap-wx.js');
Component({
  options: {
    addGlobalClass: true,
    multipleSlots: true
  },
  /**
   * 元件的屬性列表
   */
  properties: {
    weather:{
      type:String,
      value:'',
      observer:function(n,o){
        //天氣變化
      }
    },
    winfo:{
      type:Object,
      value:null,
      observer:function(n,o){
        //如果有自定義的值就使用自定義的值
          this.setData({
            obj:n
          })
      }
    }
  },

  /**
   * 元件的初始資料
   */
  data: {
    amapPlugin: null,
    key: "6799b5f6f88d3d9fb52ac244855a8759",
    obj:{},
  },
  lifetimes:{
    attached(){
      if(this.properties.winfo == null){
        this.setData({
          amapPlugin: new amap.AMapWX({
            key: this.data.key
          })
        },()=>{
          this.getWeather()
        })
      }
    }
  },
  /**
   * 元件的方法列表
   */
  methods: {
    //獲取天氣資料
  getWeather:function(){
    wx.showLoading({
      title: '請稍候...'
    })

    // type:天氣的型別。預設是live(實時天氣),可設定成forecast(預報天氣)。
    // city:城市對應的adcode,非必填。為空時,基於當前位置所在區域。 如:440300,返回深圳市天氣
    // success(data) :呼叫成功的回撥函式。
    // fail(info) :呼叫失敗的回撥函式。
    this.data.amapPlugin.getWeather({
      success: (data) =>{
        //成功回撥
        console.log(data)
        wx.hideLoading()
        this.setData({
          obj:data.liveData,
          
        })
        if(this.properties.weather == ''){
          this.setData({
            weather:data.liveData.weather
          })
        }
      },
      fail: function (info) {
        //失敗回撥
        console.log(info)
      }
    })
  },
  }
})

uweather.wxml

<view class="padding-sm">
		<view class="bg-gradual-blue padding radius shadow-blur" style="display: flex;flex-direction: row;">
			<view style="width:50%;height:100%;color:#1A94E6;">
				<view class="icon sun-shower " wx:if="{{weather == '太陽雨'}}">
					<view class="cloud"></view>
					<view class="sun"><view class="rays"></view></view>
					<view class="rain"></view>
				</view>
				<view class="icon sun-shower " wx:if="{{weather == '多雲'}}">
					<view class="cloud"></view>
					<view class="sun"><view class="rays"></view></view>
				</view>
				<view class="icon thunder-storm" wx:if="{{weather == '雷'}}">
					<view class="cloud"></view>
					<view class="lightning">
						<view class="bolt"></view>
						<view class="bolt"></view>
					</view>
				</view>
				<view class="icon cloudy" wx:if="{{weather == '陰'}}">
					<view class="cloud"></view>
					<view class="cloud"></view>
				</view>
				<view class="icon flurries" wx:if="{{weather == '雪'}}">
					<view class="cloud"></view>
					<view class="snow">
						<view class="flake"></view>
						<view class="flake"></view>
					</view>
				</view>
				<view class="icon sunny" wx:if="{{weather == '晴'}}">
					<view class="sun"><view class="rays"></view></view>
				</view>
				<view class="icon rainy" wx:if="{{weather == '雨'}}"><view class="cloud"></view></view>
			</view>
			<!--文字部分-->
			<view style="width:50%;height:100%;">
		<view class="title">
			<view class="text-cut" style="margin-top:20rpx;">{{obj.province}}-{{obj.city}}</view>
			<!--view class="text-cut">溼度:{{obj.humidity.data}}</view-->
			<view class="text-cut" style="margin-top:20rpx;">溫度:{{obj.temperature}}℃</view>
			<view class="text-cut" style="margin-top:20rpx;">天氣:{{obj.weather}}</view>
			<view class="text-cut" style="margin-top:20rpx;">{{obj.winddirection}}風{{obj.windpower}}級</view>
		</view>
			</view>
		</view>
	</view>

uweather.wxss



body {
  max-width: 42em;
  padding: 2em;
  margin: 0 auto;
  color: #161616;
  font-family: 'Roboto', sans-serif;
  text-align: center;
  background-color: currentColor;
}

h1 {
  margin-bottom: 1.375em;
  color: #fff;
  font-weight: 100;
  font-size: 2em;
  text-transform: uppercase;
}
p,
a {
  color: rgba(255,255,255,0.3);
  font-size: small;
}
p { margin: 1.375rem 0; }

.icon {
  position: relative;
  display: inline-block;
  width: 12em;
  height: 10em;
  font-size: 1em; /* control icon size here */
}

.cloud {
  position: absolute;
  z-index: 1;
  top: 50%;
  left: 50%;
  width: 3.6875em;
  height: 3.6875em;
  margin: -1.84375em;
  background: currentColor;
  border-radius: 50%;
  box-shadow:
    -2.1875em 0.6875em 0 -0.6875em,
    2.0625em 0.9375em 0 -0.9375em,
    0 0 0 0.375em #fff,
    -2.1875em 0.6875em 0 -0.3125em #fff,
    2.0625em 0.9375em 0 -0.5625em #fff;
}
.cloud:after {
  content: '';
  position: absolute;
  bottom: 0;
  left: -0.5em;
  display: block;
  width: 4.5625em;
  height: 1em;
  background: currentColor;
  box-shadow: 0 0.4375em 0 -0.0625em #fff;
}
.cloud:nth-child(2) {
  z-index: 0;
  background: #fff;
  box-shadow:
    -2.1875em 0.6875em 0 -0.6875em #fff,
    2.0625em 0.9375em 0 -0.9375em #fff,
    0 0 0 0.375em #fff,
    -2.1875em 0.6875em 0 -0.3125em #fff,
    2.0625em 0.9375em 0 -0.5625em #fff;
  opacity: 0.3;
  transform: scale(0.5) translate(6em, -3em);
  animation: cloud 4s linear infinite;
}
.cloud:nth-child(2):after { background: #fff; }

.sun {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 2.5em;
  height: 2.5em;
  margin: -1.25em;
  background: currentColor;
  border-radius: 50%;
  box-shadow: 0 0 0 0.375em #fff;
  animation: spin 12s infinite linear;
}
.rays {
  position: absolute;
  top: -2em;
  left: 50%;
  display: block;
  width: 0.375em;
  height: 1.125em;
  margin-left: -0.1875em;
  background: #fff;
  border-radius: 0.25em;
  box-shadow: 0 5.375em #fff;
}
.rays:before,
.rays:after {
  content: '';
  position: absolute;
  top: 0em;
  left: 0em;
  display: block;
  width: 0.375em;
  height: 1.125em;
  transform: rotate(60deg);
  transform-origin: 50% 3.25em;
  background: #fff;
  border-radius: 0.25em;
  box-shadow: 0 5.375em #fff;
}
.rays:before {
  transform: rotate(120deg);
}
.cloud + .sun {
  margin: -2em 1em;
}

.rain,
.lightning,
.snow {
  position: absolute;
  z-index: 2;
  top: 50%;
  left: 50%;
  width: 3.75em;
  height: 3.75em;
  margin: 0.375em 0 0 -2em;
  background: currentColor;
}

.rain:after {
  content: '';
  position: absolute;
  z-index: 2;
  top: 50%;
  left: 50%;
  width: 1.125em;
  height: 1.125em;
  margin: -1em 0 0 -0.25em;
  background: #0cf;
  border-radius: 100% 0 60% 50% / 60% 0 100% 50%;
  box-shadow:
    0.625em 0.875em 0 -0.125em rgba(255,255,255,0.2),
    -0.875em 1.125em 0 -0.125em rgba(255,255,255,0.2),
    -1.375em -0.125em 0 rgba(255,255,255,0.2);
  transform: rotate(-28deg);
  animation: rain 3s linear infinite;
}

.bolt {
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -0.25em 0 0 -0.125em;
  color: #fff;
  opacity: 0.3;
  animation: lightning 2s linear infinite;
}
.bolt:nth-child(2) {
  width: 0.5em;
  height: 0.25em;
  margin: -1.75em 0 0 -1.875em;
  transform: translate(2.5em, 2.25em);
  opacity: 0.2;
  animation: lightning 1.5s linear infinite;
}
.bolt:before,
.bolt:after {
  content: '';
  position: absolute;
  z-index: 2;
  top: 50%;
  left: 50%;
  margin: -1.625em 0 0 -1.0125em;
  border-top: 1.25em solid transparent;
  border-right: 0.75em solid;
  border-bottom: 0.75em solid;
  border-left: 0.5em solid transparent;
  transform: skewX(-10deg);
}
.bolt:after {
  margin: -0.25em 0 0 -0.25em;
  border-top: 0.75em solid;
  border-right: 0.5em solid transparent;
  border-bottom: 1.25em solid transparent;
  border-left: 0.75em solid;
  transform: skewX(-10deg);
}
.bolt:nth-child(2):before {
  margin: -0.75em 0 0 -0.5em;
  border-top: 0.625em solid transparent;
  border-right: 0.375em solid;
  border-bottom: 0.375em solid;
  border-left: 0.25em solid transparent;
}
.bolt:nth-child(2):after {
  margin: -0.125em 0 0 -0.125em;
  border-top: 0.375em solid;
  border-right: 0.25em solid transparent;
  border-bottom: 0.625em solid transparent;
  border-left: 0.375em solid;
}

.flake:before,
.flake:after {
  content: '\2744';
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -1.025em 0 0 -1.0125em;
  color: #fff;

  opacity: 0.2;
  animation: spin 8s linear infinite reverse;
}
.flake:after {
  margin: 0.125em 0 0 -1em;
  font-size: 1.5em;
  opacity: 0.4;
  animation: spin 14s linear infinite;
}
.flake:nth-child(2):before {
  margin: -0.5em 0 0 0.25em;
  font-size: 1.25em;
  opacity: 0.2;
  animation: spin 10s linear infinite;
}
.flake:nth-child(2):after {
  margin: 0.375em 0 0 0.125em;
  font-size: 2em;
  opacity: 0.4;
  animation: spin 16s linear infinite reverse;
}


/* Animations */ 

@keyframes spin {
  100% { transform: rotate(360deg); }
}

@keyframes cloud {
  0% { opacity: 0; }
  50% { opacity: 0.3; }
  100% {
    opacity: 0;
    transform: scale(0.5) translate(-200%, -3em);
  }
}

@keyframes rain {
  0% {
    background: #0cf;
    box-shadow:
      0.625em 0.875em 0 -0.125em rgba(255,255,255,0.2),
      -0.875em 1.125em 0 -0.125em rgba(255,255,255,0.2),
      -1.375em -0.125em 0 #0cf;
  }
  25% {
    box-shadow:
      0.625em 0.875em 0 -0.125em rgba(255,255,255,0.2),
      -0.875em 1.125em 0 -0.125em #0cf,
      -1.375em -0.125em 0 rgba(255,255,255,0.2);
  }
  50% {
    background: rgba(255,255,255,0.3);
    box-shadow:
      0.625em 0.875em 0 -0.125em #0cf,
      -0.875em 1.125em 0 -0.125em rgba(255,255,255,0.2),
      -1.375em -0.125em 0 rgba(255,255,255,0.2);
  }
  100% {
    box-shadow:
      0.625em 0.875em 0 -0.125em rgba(255,255,255,0.2),
      -0.875em 1.125em 0 -0.125em rgba(255,255,255,0.2),
      -1.375em -0.125em 0 #0cf;
  }
}

@keyframes lightning {
  45% {
    color: #fff;
    background: #fff;
    opacity: 0.2;
  }
  50% {
    color: #0cf;
    background: #0cf;
    opacity: 1;
  }
  55% {
    color: #fff;
    background: #fff;
    opacity: 0.2;
  }
}

參考文件

入門指南-微信小程式SDK | 高德地圖API (amap.com)

天氣查詢-API文件-開發指南-Web服務 API | 高德地圖API (amap.com)

校園小程式: 基於強智教務系統的校園服務類小程式--多校版本(預設 山科)使用雲開發 (gitee.com)

相關文章